Introduzione Alla Programmazione Dei SAD

132
Developing and managing Automatic Measurement Systems in C# M. Parvis This material created with the contribution of the students of the now defuncted II Faculty of Engineering Vercelli R.I.P. ...atque, ubi solitudinem faciunt, pacem appellant Tacitus September 2010

Transcript of Introduzione Alla Programmazione Dei SAD

Page 1: Introduzione Alla Programmazione Dei SAD

Developing and managing

Automatic Measurement Systems in

C#

M. Parvis

This material created with thecontribution of the students of the

now defuncted

II Faculty of Engineering

Vercelli

R.I.P.

...atque, ubi solitudinem faciunt, pacem appellant

Tacitus

September 2010

Page 2: Introduzione Alla Programmazione Dei SAD

Contents

1 Legalese 4

2 Forewords 5

3 Where can I find how to ? 8

4 Getting started: my first C# program 104.1 Nomenclature- part I . . . . . . . . . . . . . . . . . . . . . . . 104.2 Create the program . . . . . . . . . . . . . . . . . . . . . . . . 114.3 Nomenclature- part II . . . . . . . . . . . . . . . . . . . . . . 174.4 Revision questions . . . . . . . . . . . . . . . . . . . . . . . . 19

5 Basic operations in C# useful in data acquisition programs 205.1 Formatting and parsing data . . . . . . . . . . . . . . . . . . . 205.2 Measuring time intervals . . . . . . . . . . . . . . . . . . . . . 245.3 Bulk data management and the Buffer class . . . . . . . . . . 255.4 Working with files . . . . . . . . . . . . . . . . . . . . . . . . . 25

5.4.1 Reading and writing formatted data . . . . . . . . . . 265.4.2 Reading and writing binary data . . . . . . . . . . . . 275.4.3 Reading and writing bulk data . . . . . . . . . . . . . . 295.4.4 Transferring data between C# created files and Scilab

or Matlab . . . . . . . . . . . . . . . . . . . . . . . . . 315.5 Synchronous message reporting and data request . . . . . . . . 335.6 Useful operations on arrays . . . . . . . . . . . . . . . . . . . 335.7 Errors and exceptions . . . . . . . . . . . . . . . . . . . . . . . 345.8 Dealing with threads . . . . . . . . . . . . . . . . . . . . . . . 355.9 Extending classes with new and override . . . . . . . . . . . . 405.10 Using delegates to deal with the cross-threading problem . . . 42

1

Page 3: Introduzione Alla Programmazione Dei SAD

5.10.1 Using a delegate in the program . . . . . . . . . . . . . 435.10.2 Using a control that is extended to deal with the dele-

gate approach . . . . . . . . . . . . . . . . . . . . . . . 445.11 Revision questions . . . . . . . . . . . . . . . . . . . . . . . . 47

6 Drawing a signal evolution 496.1 General considerations . . . . . . . . . . . . . . . . . . . . . . 496.2 Doing it in C#: the simple way . . . . . . . . . . . . . . . . . 556.3 Doing it in C#: using a double buffer . . . . . . . . . . . . . . 616.4 Doing it in C#: handling the form resize and using thick pens 646.5 Revision questions . . . . . . . . . . . . . . . . . . . . . . . . 69

7 Using the Serial line 707.1 Generalities . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707.2 Using the serial line taking advantage of the IDE toolbox . . . 717.3 Manual serial line addition and cross thread solution . . . . . 797.4 Revision questions . . . . . . . . . . . . . . . . . . . . . . . . 82

8 Using the National Instrument IEEE488 838.1 Simple data polling . . . . . . . . . . . . . . . . . . . . . . . . 838.2 Waiting for acquisition end without using the SRQ events . . . 858.3 Using the SRQ in event mode . . . . . . . . . . . . . . . . . . 878.4 Revision questions . . . . . . . . . . . . . . . . . . . . . . . . 89

9 Using the Hewlett Packard IEEE488 929.1 Simple data polling . . . . . . . . . . . . . . . . . . . . . . . . 929.2 Waiting for acquisition end without using the SRQ events . . . 959.3 Using the SRQ in event mode . . . . . . . . . . . . . . . . . . 979.4 Revision questions . . . . . . . . . . . . . . . . . . . . . . . . 100

10 Using the National Instrument DAQ Boards 10110.1 Forewords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10110.2 Generalities . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10210.3 Data reading . . . . . . . . . . . . . . . . . . . . . . . . . . . 10310.4 Generating voltages . . . . . . . . . . . . . . . . . . . . . . . . 10810.5 Using digital inputs and outputs . . . . . . . . . . . . . . . . . 11010.6 Performing continuous acquisitions . . . . . . . . . . . . . . . 11210.7 Using DAQ boards in trigger mode . . . . . . . . . . . . . . . 115

2

Page 4: Introduzione Alla Programmazione Dei SAD

10.8 Manual acquisition fine tuning . . . . . . . . . . . . . . . . . . 11610.8.1 Manually setting the acquisition timing . . . . . . . . . 11710.8.2 Manually setting the input gain . . . . . . . . . . . . . 118

10.9 Revision questions . . . . . . . . . . . . . . . . . . . . . . . . 118

11 Interface installation and useful links 12011.1 National Instrument . . . . . . . . . . . . . . . . . . . . . . . 12011.2 Agilent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120

12 GPL Licence 12312.1 Preamble . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12312.2 TERMS AND CONDITIONS FOR

COPYING, DISTRIBUTION ANDMODIFICATION . . . . . . . . . . . . . . . . . . . . . . . . . 124

12.3 How to Apply These Terms to Your New Programs . . . . . . 130

3

Page 5: Introduzione Alla Programmazione Dei SAD

Chapter 1

Legalese

All the material of this primer is copyrighted by Politecnico di Torino anddistributed under the GPL licence

The software here described has been developed with freely available tools

• Visual Studio express edition http://www.microsoft.com,

• Java and NetBeans http://java.sun.com,

• Scilab http://www.scilab.org,

• OpenOffice http://www.openoffice.org

This document written in Latex and converted into pdf by using the latexdistribution MixTex available at http://www.miktex.org.

4

Page 6: Introduzione Alla Programmazione Dei SAD

Chapter 2

Forewords

This is not a course on C#. If you wish to learn to program in C# becauseyou wish to became a programmer you are not in the right place.

This primer is designed to allow students of courses of master degreeregarding Automatic Measurement Systems at Politecnico di Torino to getacquainted with general purpose methods for data acquisition and data viewfrom instruments and data acquisition boards.

Custom automatic measurements systems can basically be arranged intwo ways:

• by using integrated environments such as Labview c©, which hide mostof the details of the communication with the instruments

• by writing a program in one of the available languages (C, C++, C#,Java, Visual Basic, Pyton,...) taking care of all the details of the com-munication and of the instrument set-up

The first approach at first seems more attractive since you can arrange acomplete data acquisition system with little or no effort, however it requiresyou (or your company) to invest a not negligible amount of money buying theenvironment and often requires you to pay royalties if you want to distributeyour programs to your clients. Learning to arrange acquisition systems byusing this approach might be a reasonable solution for a bachelor studentwho does not have the goal of being capable of developing new and trickyapplications.

The second approach is more complex, requires you to study a program-ming language, to understand the instrument communication protocols and

5

Page 7: Introduzione Alla Programmazione Dei SAD

several programming issues, but gives you a tremendous flexibility and thepossibility to arrange sophisticated solutions. In addition you (or your com-pany) do not have to invest large amounts of money in the developmentsoftware and you are not bothered with royalty issues. This is a reasonableway to learn for a master student whose goal is to be capable of developingcomplex systems and to provide innovation. Once you learned developing asystem in this way, the downgrade to the integrated environment is somehowtrivial.

At this point the question is: which programming language? The correctanswer to this question is... any language: once you understand the instru-ment protocol and its programming issues, most of the effort is done; theactual coding is a small part of the job! Moreover all the object-orientedlanguages are similar (from the point of view of using them to arrange ameasurement system): once you learned to code in a language, moving toanother language is a matter of syntax and few other details.

So why C#? The answer is that probably the best solution would beJava, which is fully portable among different platforms, however most ofdata acquisition boards and instrument interfaces lack drivers designed forJava. This is probably the price you have to pay for the Java characteristicto be machine independent, but in our case this makes things a bit morecomplicated. If we had time to develop a more structured work, we could usea solution which gives quite interesting results i.e. we could develop a simpleprogram probably written in C (or C++) which is capable of interfacing theinstrument and which does not have a graphic interface, but opens a networksocket to interchange data with other either local o remote programs. Thenwe could use Java to design the graphic interface and connect via socket toour C ’daemon’ program. This type of dual-layer solution is widely used inthe measurement system arena, but is complex to arrange.

C# instead (at least in the Windows c©environment) can directly use thedriver provided by the interface manufacturers and has a syntax almost equalto Java so that, if you decided to move your code to Java, you might do thiswithout problems.

This tutorial comprises a set of C# solutions

• MyFirstApplication and MySecondApplication: the starting point

• DrawExample, DrawExampleBuffer, and DrawExampleBufferResize:how to draw graphics

6

Page 8: Introduzione Alla Programmazione Dei SAD

• ParsingAndFormatting and WorkingWithFiles: basic operation in C#

• BackgroundThread: advanced operations in C#

• SerialLineUse.Csharp and SerialLineUseManual: how to use the RS232line

• NI488.Csharp, NI488-SrqPolling.CSharp, NI488-SrqEvent: how to usea National Instrument IEEE488

• HP-IEEE488.BasicRead.Csharp, HP-IEEE488-SrqPolling.CSharp, HP-IEEE488-SrqEvent: how to use an Agilent IEEE488

• NIDAQ.Csharp: how to use a National Instrument Data AcquisitionBoard

• CallBackAcquisition: how to perform a continuous data acquisition

If you wish to learn more on C# there are a lot of resources on the internetyou can browse for free. Simply google C# tutorial.

Among the courses (as for today) you can go to

• http://www.csharp-station.com

• http://www.ssw.uni-linz.ac.at/Teaching/Lectures/CSharp/Tutorial/

• http://msdn.microsoft.com/en-us/library/aa288436.aspx

• http://www.java2s.com/Tutorial/CSharp/CatalogCSharp.htm

BEWARE:the examples have been written for a specific C# version (i.e. Visual Studio2008) and for the instrument and data acquisition drivers available at thetime of writing. While the program basics remain the same as the softwareversions change, the exact syntax might change so be prepared to encounterand manage compiler warnings and errors.

7

Page 9: Introduzione Alla Programmazione Dei SAD

Chapter 3

Where can I find how to ?

• Make a C# program

• Solve the missing reference error

• Block copy data from one array to another using a Buffer

• Finding max min mean... of an array

• Format a number, parsing a string

• Measuring time intervals

• Using files to save and load data. Here you can also learn how to opendata saved in C# by using Matlab and Scilab

• Having a message box to appear

• Manage and trap execution errors

• Use a background thread (advanced)

• Extend a class (advanced)

• Using delegates to deal with cross-threading (advanced)

• Draw the evolution of a signal

• Use a double buffer to draw a big amount of data

• Draw the evolution of a signal with a tick pen

8

Page 10: Introduzione Alla Programmazione Dei SAD

• Create a form you can resize for better view

• Use the serial (RS232) line to interact with instruments

• Deal with the damned CrossThreadException

• Use a National Instrument IEEE488 interface

• Use an Agilent (formerly Hewlett Packard) IEEE488 interface

• Use a National Instrument DAQ board

• Fine tuning a DAQ acquisition

• Use a National Instrument DAQ board to perform a continuous acqui-sition(advanced)

• Install the drivers for NI and Agilent boards

• Revision questions to have an idea of my capabilities: at the end ofeach section you can find some revision questions

9

Page 11: Introduzione Alla Programmazione Dei SAD

Chapter 4

Getting started: my first C#program

4.1 Nomenclature- part I

Before creating our first program we need to speak the same meta-languageso let us introduce some concepts.

C# is an object-based language i.e. most of the ’things’ that compose aprogram are ’objects’. An object is a ’thing’ that:

• belongs to a specific class of objects (in the code it is actually a class).Examples of classes of objects are Forms, Buttons, ....

• is identified by a unique name

• has to be ’created’ (and sometime destroyed). The creation is obtainedwith the keyword new; the C# usually takes care of destroying no longerrequired objects, however sometimes an explicit destruction is requiredby properly using the keyword dispose.

• has certain properties which can be altered during the object life. Ex-amples of properties for graphic objects are size, color,....

• is capable of doing certain operations. The ’supported’ operations de-pend on the object type. Examples of operations are capability ofdisplaying a text or a geometric shape or an image and so on. Thedifferent operations are requested by invoking the object ’methods’

10

Page 12: Introduzione Alla Programmazione Dei SAD

• is capable of reacting to certain ’events’. Examples of events are clickor double click with the mouse and so on. The reaction to a specificevent depends on the object type, but also on the code you decide towrite to ’handle’ the events you are interested in.

Each object is therefore associated to three elements: properties, methods,events. The access (or invocation) of properties and methods is obtained bywriting the property (method) name after the object name separating themwith a dot:

anObjectName.aPropertyName = newValue;

anObjectName.aMethod(parameters);

note that the property update makes use of the equals sign to assign a newvalue, while the method does not use it.

The event use is more complex since we need to associate some code wewrite to the event. This is done with the syntax:

anObjectName.aEvent +=

anObjectThatHandleEvents(aProcedure);

where anObjectThatHandleEvents is a special wrapper usually provided bythe system with the object itself while aProcedure is the procedure we haveto write and that must conform some constraints. We will discuss thesedetails later.

4.2 Create the program

Start the C# IDE so that you get the welcome screen (fig. 4.1Have a look at the IDE options to have it behaving as you like. I prefer

using a multiple document interface so I changed this option (fig 4.2 Sinceyou likely open and close the IDE several times during out primer, I alsosuggest you change the start-up behavior asking the IDE to re-open the lastproject.

Now we are ready to create our first project. Go to file/new/Projectand select Windows Form Application. Be sure to change the project namebefore going on since it is a bit more complicated to change it after theproject has been created (fig. 4.3. Do not worry about the project location

11

Page 13: Introduzione Alla Programmazione Dei SAD

Figure 4.1: Welcome screen for the C# IDE

Figure 4.2: Option form for the C# IDE

since you will be asked later where you want to save your work. At this pointyou have a new project with its main Form

As a first thing, you are strongly advised to change the form name fromForm1 to something different which remember you of the project purpose.To do this, go on the right panel, select the line which reads Form1 and rightclick selecting rename. Change the name and you will get a message sayingthat changing the name will also change the file name. This is exactly whatwe want since this way we will be able to easily identify our code (fig. 4.4)

You may also want to change the form caption (i.e. its text property) tosomething more interesting. To do this select the form in the main panel thenin the right panel click the button properties so that the property windowappears, look for the text property and change it (fig. 4.4)

Now it is time to populate our form: on the left you can see a buttonToolbox, click it and a window appears expand the common controls, select

12

Page 14: Introduzione Alla Programmazione Dei SAD

Figure 4.3: Create the project

a button and drag it onto the form, then select a a textbox and drag it ontothe form (fig. 4.5. Then, as usual, change the object names to buttonHello

and textBoxHello and button text property to Hello! so that it reflects itsrole. Also, for the text box, change the property Multiline to true so thatyou are not restricted to a single line box and so that you can change itsvertical size

At this point we are ready to write the code that when we press thebutton makes it to appear the word Hello!in the text box.

Double click on the button and you will be taken to the code editor withinthe procedure buttonHello_Click then write the code:

.......

private void buttonHello_Click(object sender, EventArgs e)

{

textBoxHello.Text = "Hello!";

}

.......

The program is complete and you can run it: go to Debug and Start Debugging.You will see the message the program is being compiled, then your window

13

Page 15: Introduzione Alla Programmazione Dei SAD

Figure 4.4: Renaming the base form and its associated file and accessing theform properties

appears and when you press the button, the word Hello! appears in the textbox.

The single line of code we have written is the first use we have madeof a property (actually the text property) of the object of type TextBox,however the code manages events (when we click the button..) and makesuse of the TextBox we never created! How is it possible? The answer is verysimple: the IDE takes care of object creation and link to events for us. Thecode that do this is in a a file automatically created by the IDE which hasthe name of the form followed by .Designer.cs. You see such file in thesolution explorer and you can open (and edit..) it if you want. Unless youknow what you are doing it is better you do not edit such file since you caneasily mess it up preventing the graphic editor to work. Nevertheless let uswhat it contains:

namespace MyFirstApplication

{

partial class FormHelloWorld

{

14

Page 16: Introduzione Alla Programmazione Dei SAD

Figure 4.5: Populating the form

/// <summary>

/// Required designer variable.

/// </summary>

private System.ComponentModel.IContainer components = null;

/// <summary>

/// Clean up any resources being used.

/// </summary>

/// <param name="disposing">true if

///managed resources should be disposed;

/// otherwise, false.</param>

protected override void Dispose(bool disposing)

{

if (disposing && (components != null))

{

components.Dispose();

}

base.Dispose(disposing);

}

#region Windows Form Designer generated code

private System.Windows.Forms.Button buttonHello;

15

Page 17: Introduzione Alla Programmazione Dei SAD

private System.Windows.Forms.TextBox textBoxHello;

}

}

The first part simply contains creation and disposing code which for now isof no interest for us. Then you see a line saying #region Window.... we willdiscuss below and finally you see the global declaration of the object we used:the Button and the TextBox.

If we expand the region designer generated code we see the procedureInitializeComponent() which is the procedure that create the objects (seethe keyword new), sets a lot of object properties and register the handler forthe button click (see the linethis.buttonHello.Click += new ....

#region Windows Form Designer generated code

/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

private void InitializeComponent()

{

this.buttonHello = new System.Windows.Forms.Button();

this.textBoxHello = new System.Windows.Forms.TextBox();

this.SuspendLayout();

//

// buttonHello

//

this.buttonHello.Location = new System.Drawing.Point(31, 23);

this.buttonHello.Name = "buttonHello";

this.buttonHello.Size = new System.Drawing.Size(75, 23);

this.buttonHello.TabIndex = 0;

this.buttonHello.Text = "Hello!";

this.buttonHello.UseVisualStyleBackColor = true;

this.buttonHello.Click +=

new System.EventHandler(this.buttonHello_Click);

16

Page 18: Introduzione Alla Programmazione Dei SAD

//

// textBoxHello

//

this.textBoxHello.Location = new System.Drawing.Point(31, 79);

this.textBoxHello.Multiline = true;

this.textBoxHello.Name = "textBoxHello";

this.textBoxHello.Size = new System.Drawing.Size(205, 46);

this.textBoxHello.TabIndex = 1;

//

// FormHelloWorld

//

this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);

this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;

this.ClientSize = new System.Drawing.Size(292, 178);

this.Controls.Add(this.textBoxHello);

this.Controls.Add(this.buttonHello);

this.Name = "FormHelloWorld";

this.Text = "FormHelloWorld";

this.ResumeLayout(false);

this.PerformLayout();

}

#endregion

private System.Windows.Forms.Button buttonHello;

private System.Windows.Forms.TextBox textBoxHello;

}

}

4.3 Nomenclature- part II

To be able to understand the more complex programs we are going to realizewe need to agree about the meaning of other keyword and concept. Themost important thing to understand is the concept of thread. A thread is

17

Page 19: Introduzione Alla Programmazione Dei SAD

an execution path i.e. a block of code within which only a statement canbe executed at a time. The concept seems strange at first, but in reality isquite simple. Let us start from the bottom making reference for simplicityto a computer with a single processor.

Of course the microprocessor can execute one instruction at a time, butsince the execution is extremely fast, the operating system is able to appearas if it concurrently executes different programs. This is the way you can runat the same time a word processor and listen music with your favorite player.One step more: within the same program, e.g. within the word processoryou can write words and at the same time the word processor can print;again two operations: well these two operations can appear to be executedat the same time because they belong to different code paths i.e. to differentthreads. The code within the same thread cannot allows for the execution ofmore than a single operation.

As an example, if we put the code that play music in the same threadwith the code that print the two operations cannot be executed at the sametime: either the music stops when you print or the printing does not startuntil you finish playing the music.

This problem affects our programs because in C# all the operations whichmanage the graphic interface belongs to the same thread so that is an opera-tion takes a lot to be executed, it blocksall the other operation. To see whatthis means let us to modify the previous example putting inside the buttonthe following code:

........

textBoxResult.Text = "Starting operation\r\n";

DateTime start = DateTime.Now;

long tk = start.Ticks; //ticks are increments of 100ns;

DateTime stop=DateTime.Now;

//this takes 5 s to be executed

while((stop.Ticks-tk)<5e7){

stop=DateTime.Now;

}

textBoxResult.AppendText("Operation complete");

.......

As you see the code write a message in the text box, then performs a fivesecond operation and finally writes another message

18

Page 20: Introduzione Alla Programmazione Dei SAD

Run the code: push the button and look: no message appears, but after5s the massages appear together. It is worse than what you think: pushthe button again and cover the uncover the window with some other win-dow programs: your window is not repainted! The reason of course is thatthe window painting is performed by code within the same thread that isexecuting the loop so that until you finish the loop no other operation takesplace.

Note that for this specific case there is a simple work around which is toadd inside the loop the statement

Application.DoEvents();

which instructs the program to interrupt the code, let the operating systemsee if something graphic has to be done; allow it to do graphics operations andeventually restart the code. Unfortunately this work around is not alwaysuseful and moreover it is dangerous: try running the code and push thebutton twice: as you see the behavior is a bit strange...

The definite solution to this problem is to perform the long operation ina different thread, by explicitly creating a new thread. Using correctly thethreading is not an easy task; some of hints will be presented later (see Usea background thread)

4.4 Revision questions

• what is the difference between methods and properties ?

• what is an event how it is managed?

• where all the details about the graphic interface are stored? how canbe edited?

• what is the meaning of Application.DoEvents() and when could youneed to use it?

19

Page 21: Introduzione Alla Programmazione Dei SAD

Chapter 5

Basic operations in C# usefulin data acquisition programs

This (long) chapter contains most of the basic things you need to write yourdata acquisition program. Use this chapter as a cookbook to develop yourprogram.

5.1 Formatting and parsing data

The complete source code of this example is available in theParsingAndFormatting package

One of the commonest task in writing programs for data acquisition andanalysis is to parse values which arrives from the instruments in the form ofstrings to obtain numbers and to print results of data processing in formsand text boxes. While this should be a trivial task, problems arises due tothe international settings of the computers.

The common way to approach the parsing of a string is to use theDouble.Parse (or Float.parse, ....) in the form:

double aNumber=Double.Parse(aString);

Unfortunately this solution is quite dangerous since it assumes that the sym-bol used to separate the integra part from the fractional part is the onedefined in the so called Culture (i.e. international setting) of the computeron which your program is running (which can be different from the culture

20

Page 22: Introduzione Alla Programmazione Dei SAD

of the computer on which you developed the software!). Basically there aretwo possibilities: the separator can be the dot for the English culture andthe comma for the Italian culture. Everything would be right if all programsand instruments used the same convention, but this is not true!.

If you write a program which get data from instruments following theIEEE488.2 specifications the data comes with the dot as the separator there-fore using the wrong convention (...the Italian one...) leads to catastrophicresults.

The solution is to avoid the simplified syntax shown above and to explic-itly set the convention you want to use. To do this you need to import thedefinitions connected to the globalization by adding at top of your programthe line;

using System.Globalization;

Then each time want to perform a data parsing you have to set a cultureobject and use it

CultureInfo cEn = new CultureInfo("en-US");

double aNumber = Double.Parse(aString, cEn);

A similar approach HAS to be followed when formatting data. Such anoperation is obtained with the command:

CultureInfo cEn = new CultureInfo("en-US");

String aString = String.Format(cEn, "{0:##.##}", tst);

The syntax is quite straightforward since for each parameter you want toformat you have to add a format descriptor of type "{0:##.##}" where thenumber is the ordinal position of the parameter and the # are placeholderfor digits. Please observe that the separator between integral and fractionalpart is the dot, while the separator used in the actual formatting is definedin the culture you are using.

The following fragment of code shows some more examples and highlighthow a wrong combination of culture and separator can lead to error situa-tions.

21

Page 23: Introduzione Alla Programmazione Dei SAD

private void buttonParse_Click(object sender, EventArgs e)

{

//define objects for English and Italian cultures

CultureInfo cIta = new CultureInfo("it-IT");

CultureInfo cEn = new CultureInfo("en-US");

String baseString = "1.2345";

double tst = Double.Parse(baseString, cEn);

String ss = String.Format(cEn, "{0:##.####}", tst);

textBoxParse.AppendText("parsing "+baseString+

"with EN culture gives:" + ss + "\r\n");

//if you are with italian culture you get a strange result

tst = Double.Parse(baseString, cIta);

ss = String.Format(cEn, "{0:##.####}", tst);

textBoxParse.AppendText("parsing "+baseString+

"with IT culture gives " + ss + "\r\n");

baseString = "1,2345";

try

{

tst = Double.Parse(baseString, cEn);

ss = String.Format(cEn, "{0:##.####}", tst);

textBoxParse.AppendText("parsing " + baseString +

" with EN culture gives:" + ss + "\r\n");

}

catch (FormatException ef)

{

textBoxParse.AppendText("parsing " + baseString +

" with EN culture gives:" + ef.Message+ "\r\n");

}

tst = Double.Parse(baseString, cIta);

ss = String.Format(cEn, "{0:##.####}", tst);

textBoxParse.AppendText("parsing " + baseString +

" with IT culture gives " + ss + "\r\n");

}

private void buttonFormat_Click(object sender, EventArgs e)

22

Page 24: Introduzione Alla Programmazione Dei SAD

{

//define objects for English and Italian cultures

CultureInfo cIta = new CultureInfo("it-IT");

CultureInfo cEn = new CultureInfo("en-US");

double tst = 1.0 / 3;

String ss = String.Format(cEn, "{0:##.##}", tst);

textBoxFormat.AppendText

("Formatting 1/3 with format {0:##.##} in EN culture gives "

+ ss + "\r\n");

ss = String.Format(cIta, "{0:##.##}", tst);

textBoxFormat.AppendText

("Formatting 1/3 with format {0:##.##} in IT culture gives "

+ ss + "\r\n");

ss = String.Format(cEn, "{0:#0.##}", tst);

textBoxFormat.AppendText

("Formatting 1/3 with format {0:#0.##} in EN culture gives "

+ ss + "\r\n");

ss = String.Format(cIta, "{0:#0.##}", tst);

textBoxFormat.AppendText

("Formatting 1/3 with format {0:#0.##} in IT culture gives "

+ ss + "\r\n");

//with the culture you can change the

//aspect of date and time representation

cEn.DateTimeFormat.ShortDatePattern = "yyyy-mm-dd";

cEn.DateTimeFormat.ShortTimePattern = "HH:mm:ss";

DateTime dd = DateTime.Now;

ss = String.Format(cEn, "{0:d}", dd);

textBoxFormat.AppendText

("printing Custom date yyyy-mm-dd " + ss + "\r\n");

ss = String.Format(cEn, "{0:t}", dd);

textBoxFormat.AppendText

23

Page 25: Introduzione Alla Programmazione Dei SAD

("printing Custom time HH:mm:ss " + ss + "\r\n");

cEn.DateTimeFormat.ShortTimePattern = "hh:mm:ss";

ss = String.Format(cEn, "{0:t}", dd);

textBoxFormat.AppendText

("printing Custom time hh:mm:ss " + ss + "\r\n");

}

}

The result of these settings is shown in Fig. 5.1

Figure 5.1: Result of different format and parsing options

5.2 Measuring time intervals

The measurement of time intervals, as an example to see how long an opera-tion is, is quite simple and obtained by means of the DateTime object. TheDateTime object contains methods to deal with time, dates and time anddate intervals, plus the method Ticks that returns the number of hundredof nanoseconds since midnight January 1 year 1 and the method Now thatreturns the actual time. Using two variables and reporting the difference istherefore an easy way to measure time intervals:

DateTime startTime = DateTime.Now;

24

Page 26: Introduzione Alla Programmazione Dei SAD

.......

DateTime stopTime = DateTime.Now;

TimeSpan elapsedTime = stopTime - startTime;

this.textBoxTime.Text = String.Format("{0:000.000 ms}",

elapsedTime.Ticks*1e-4);

5.3 Bulk data management and the Buffer

class

Sometimes you need to change the way a data block is interpreted i.e. youneed to cast a value without changing its binary nature. This happenswhen you receive a raw stream of data (e.g. from a network socket, froman interface, reading a file,....) and you need to interpret the data as eitherinteger or floating point number. The common way to receive the datastream is by means of an array of bytes and of course you can convert thebytes manually regardless of the encoding they have (provided that you knowthe encoding). However, if the encoding is a standard one (including the byteorder i.e. the big-endian, little-endian issue), you can convert all the streamat once by using the Buffer static class. The Buffer can convert one-to-one bytes into any other primitive type (int, long, float double,...) withoutapplying any types of processing. Even thought the class has other methods,the most important one is the BlockCopy that copies one array into another

Buffer.BlockCopy( sourceArray,

sourceOffset,

destArray

destOffset,

byteCount);

Be warned that the destination array must be allocated in advance beforecopying to it. For an example of Buffer use refer to Reading and writingbulk data in the following section

5.4 Working with files

The complete source code of this example is available in theWorkingWithFiles package

25

Page 27: Introduzione Alla Programmazione Dei SAD

5.4.1 Reading and writing formatted data

Another common operation when writing software automatic test equipmentsis the reading and writing of data into files. Two types of files can be used:ASCII and binary. Ascii files can easily be read by other programs suchas notepad, but are in general bigger and slower than binary files. On thecontrary binary files a more compact and efficient but cannot be easily readby other programs since you need to know in advance how they have beenwritten. In addition Ascii files can only be opened for writing o reading butnot both and can only be read sequentially while such limitations do notapply to the binary files.

In both cases you need to import the definitions of input output:

using System.IO;

Then the file is accessed through a FileStream object

FileStream file = new FileStream(aFileName,

FileMode.OpenOrCreate, FileAccess.Write);

After this for the ascii file you have to use two objects of type StreamReaderand StreamWriter while the binary file are accessed through BinaryWriter

and BinaryReader

This code fragment opens a file in ascii mode writes ten numbers andthen read them back. Note that since the file is opened in ascii mode toread the values back it is necessary to close the file and open it again in readmode.

private void buttonAsciiFile_Click(object sender, EventArgs e)

{

//beware: when working with the IDE the startup path is

//the either sub dir bin/debug or bin/release

String myPath = System.Windows.Forms.Application.StartupPath;

//text file

//open the file in write mode

FileStream file = new FileStream(myPath + "\\test.txt",

FileMode.OpenOrCreate, FileAccess.Write);

StreamWriter sw = new StreamWriter(file); //create writer

CultureInfo cEn = new CultureInfo("en-US");

26

Page 28: Introduzione Alla Programmazione Dei SAD

for (int i=0;i<10;i++){

String s=String.Format("{0:##.###}\r\n",i);

sw.Write(s);

}

sw.Close(); //close writer

file.Close(); //close file

//re-open the file in read mode

file = new FileStream(myPath + "\\test.txt",

FileMode.Open, FileAccess.Read);

StreamReader sr = new StreamReader(file); //create reader

String sRead = sr.ReadToEnd(); //read

textBoxRes.Text = "reading from test.txt\r\n";

textBoxRes.AppendText(sRead);

sr.Close(); //close reader

file.Close(); //close file

}

5.4.2 Reading and writing binary data

This code fragment opens a file for contemporaneous write and read in binarymode. This time we do not need to close the file after writing and re-open itfor reading, however when we read we must know what to read (integers inthe first part and doubles in the second part)

private void buttonWriteBinary_Click(object sender, EventArgs e)

{

//beware: when working with the IDE the startup path is

//the either sub dir bin/debug or bin/release

String myPath = System.Windows.Forms.Application.StartupPath;

//open the file for both read and write

FileStream file = new FileStream(myPath + "\\test.bin",

FileMode.OpenOrCreate,

FileAccess.ReadWrite);

BinaryWriter swB = new BinaryWriter(file); //create writer

BinaryReader srB = new BinaryReader(file); //create reader

27

Page 29: Introduzione Alla Programmazione Dei SAD

//write 5 integers 32 bits

for (int x = 0; x < 5; x++)

swB.Write(x);

//write 5 doubles

for (int x = 0; x < 5; x++)

{

double d = x;

swB.Write(d);

}

//rewind the file

file.Seek(0, SeekOrigin.Begin);

CultureInfo cEn = new CultureInfo("en-US");

textBoxRes.Text = "Read from test.bin file\r\n";

//read the 5 integer

for (int x = 0; x < 5; x++)

{

int r = srB.ReadInt32();

textBoxRes.AppendText(String.Format(cEn,"{0:000 }\r\n", r));

}

//read the 5 double

for (int x = 0; x < 5; x++)

{

double d = srB.ReadDouble();

textBoxRes.AppendText(String.Format(cEn, "{0:000 }\r\n", d));

}

swB.Close(); //close writer

srB.Close(); //close reader

file.Close(); //close file

}

28

Page 30: Introduzione Alla Programmazione Dei SAD

5.4.3 Reading and writing bulk data

This code fragment opens a file for contemporaneous write and read in binarymode as in the previous code, but in this case we use the Buffer class tospeedup the operation. If you want to check the speed remember to runthe program twice, since the first time the system needs time to allocate thearea on disk and this makes the comparison unfair. You may expect a speedimprovement of a factor two or more when using the Buffer

private void buttonBulk_Click(object sender, EventArgs e)

{

//bulk write

//load data

String myPath = System.Windows.Forms.Application.StartupPath;

int nData = 10000000;

double[] dd = new double[nData];

for (int i = 0; i < dd.Length; i++)

{

dd[i] = i;

}

FileStream file = new FileStream(myPath + "\\testBulk.bin",

FileMode.OpenOrCreate,

FileAccess.ReadWrite);

BinaryWriter swB = new BinaryWriter(file); //create writer

BinaryReader srB = new BinaryReader(file); //create reader

//monitor the time to perform the operation

DateTime startTime = DateTime.Now;

byte[] bb = new byte[dd.Length * 8];

//copy data into a byte array

Buffer.BlockCopy(dd, 0, bb, 0, dd.Length * 8);

//write the byte array

swB.Write(bb);

DateTime stopTime = DateTime.Now;

TimeSpan elapsedTime = stopTime - startTime;

this.textBoxRes.AppendText(

String.Format(

"{0:000.000 ms} to write {1:00000} double bulk\r\n",

29

Page 31: Introduzione Alla Programmazione Dei SAD

elapsedTime.Ticks * 1e-4, nData));

//now bulk read

//rewind

file.Seek(0, SeekOrigin.Begin);

startTime = DateTime.Now;

bb = new byte[dd.Length * 8];

srB.Read(bb, 0, bb.Length);

//copy data into double

dd = new double[nData];

Buffer.BlockCopy(bb, 0, dd, 0, dd.Length * 8);

stopTime = DateTime.Now;

elapsedTime = stopTime - startTime;

this.textBoxRes.AppendText(

String.Format(

"{0:000.000 ms} to read {1:00000} double bulk\r\n",

elapsedTime.Ticks * 1e-4, nData));

swB.Close(); //close writer

srB.Close(); //close reader

file.Close(); //close file

//now use the conventional way

file = new FileStream(myPath + "\\testBulkConv.bin",

FileMode.OpenOrCreate,

FileAccess.ReadWrite);

swB = new BinaryWriter(file); //create writer

srB = new BinaryReader(file); //create reader

//use a buffer

startTime = DateTime.Now;

for (int i = 0; i < dd.Length; i++)

swB.Write(dd[i]);

stopTime = DateTime.Now;

elapsedTime = stopTime - startTime;

this.textBoxRes.AppendText(

String.Format(

"{0:000.000 ms} to write {1:00000} double with for\r\n",

30

Page 32: Introduzione Alla Programmazione Dei SAD

elapsedTime.Ticks * 1e-4, nData));

//now read

file.Seek(0, SeekOrigin.Begin);

startTime = DateTime.Now;

dd = new double[nData];

for (int i = 0; i < dd.Length; i++)

dd[i]=srB.ReadDouble();

stopTime = DateTime.Now;

elapsedTime = stopTime - startTime;

this.textBoxRes.AppendText(

String.Format("{0:000.000 ms} to read {1:00000} double with for\r\n",

elapsedTime.Ticks * 1e-4, nData));

swB.Close(); //close writer

srB.Close(); //close reader

file.Close(); //close file

}

}

5.4.4 Transferring data between C# created files andScilab or Matlab

Files written in C# can be easily read in Scilab. The following code showshow to read both the binary and ascii files

//scilab script to read a file written in c#

clear

//read the binary file

[fd,ee]=mopen("bin/debug/test.bin",’rb’);

intData=mget(5,’i’,fd);

doubleData=mget(5,’d’,fd);

mclose(fd);

printf (’\nBinary Read integer \n’);

for i=1:5

printf (’%i ’,intData(i));

31

Page 33: Introduzione Alla Programmazione Dei SAD

end

printf (’\nBinary Read double\n");

for i=1:5

printf (’%e ’,doubleData(i));

end

info=fileinfo("bin/debug/test.txt");

filesize=info(1);

[fd,ee]=mopen("bin/debug/test.txt",’r’);

str=mgetstr(filesize,fd);

mclose(fd);

printf (’\nAscii Read \n");

printf (’%s\n",str);

A similar script can be used by people wishing to spend money and useMatlab

%matlaqb script to read a file written in c#

clear

%read the binary file

[fd,ee]=fopen(’bin/debug/test.bin’,’rb’);

intData=fread(fd,5,’int32’);

doubleData=fread(fd,5,’double’);

fclose(fd);

disp(sprintf (’\nBinary Read integer \n’));

for i=1:5

disp(sprintf (’%i ’,intData(i)));

end

disp(sprintf (’\nBinary Read double\n’));

for i=1:5

disp(sprintf (’%e ’,doubleData(i)));

end

[fd,ee]=fopen(’bin/debug/test.txt’,’r’);

ch=fread(fd);

32

Page 34: Introduzione Alla Programmazione Dei SAD

fclose(fd);

str=setstr(ch);

disp(sprintf (’\nAscii Read \n’));

disp(sprintf (’%s\n’,str));

5.5 Synchronous message reporting and data

request

Sometimes you need to stop the program execution to communicate some-thing to the user requiring his/her attention. This can be done by means ofthe so called MessageBoxes There are several variations of this object, butthe basic flavor is simply:

MessageBox.Show("MyMessage");

When this statement is executed the program stops and a window appearsuntil the user press OK. You can customize the box by changing icon, caption,and buttons. You can also use the message to get the user response amongdifferent buttons

DialogResult r = MessageBox.Show("MyMessage", "MyCaption",

MessageBoxButtons.AbortRetryIgnore);

if (r==DialogResult.Abort)

{

....

}

else if (r == DialogResult.Retry)

{

...

}

Please note that in C# the is not an InputBox to request typed datafrom the user even though such method exists in VisualBasic.

5.6 Useful operations on arrays

Arrays in C# are objects that support some useful operation you can use tospeed up the program development. Such operations operate on the entire

33

Page 35: Introduzione Alla Programmazione Dei SAD

array to compute specific values (max, min, ...) or to copy the data to anotherarray in a single operation The most important operations are:

vector.Max();

vector.Min();

vector.Average();

vector.Sum();

while the copy operation is

vector.CopyTo(newPreAllocatedVector);

vector.CopyTo(newPreAllocatedVector,startPointInNewVector);

note that both syntaxes require the destination vector to be preallocated witha size large enough to accommodate all the elements of the origin vector; thismeans that in the second syntax the destination vector must have at leastthe length of the original vector plus the required offset. The copy operationis allowed only on one-dimensional arrays.

5.7 Errors and exceptions

The exception managing in C# is obtained by using the the try, catch,finally structure. Please note that the best way to deal with abnormalsituations is to prevent them by checking in advance for potential problems.However sometimes this is not enough so, each time you wonder an exceptionmay occur while executing a statement, you may wrap the critical statementin a safer environment:

//’read values from channels

try

{

//critical statement(s)

readTask = new Task();

readTask.AIChannels.CreateVoltageChannel("Dev1/ai0:1", "",

AITerminalConfiguration.Nrse, -10.0, 10.0,

AIVoltageUnits.Volts);

.......

}

34

Page 36: Introduzione Alla Programmazione Dei SAD

catch (DAQException exception)

{

MessageBox.Show(exception.Message);

}

catch (IOException exception)

{

......

}

finally

{

readTask.Dispose();

}

The code comprises the try{} section in which you put the code at risk;one or more catch{} sections where the execution proceeds in case of prob-lems and, optionally, a finally section which is executed regardless of whathappened before. In this last section you can perform operations such asobject disposing, file closing,... you want to be executed anyway. The catchclause(s) allow you to take different actions depending of the type of prob-lems you encountered. Unless you really know what you are doing, you arestrongly advised not to leave an exception silent, at least report it with amessage box.

Note that try/catch blocks can be nested: if an exception is not caught(i.e. an exception type does not appear in a catch clause), it goes to the outertry/catch level; if no outer level exist, the exception crash your program.

5.8 Dealing with threads

The complete source code of this example is available in theBackgroundThread package

BEWAREWhen you use a thread and graphics objects (i.e. TextBoxes, Buttons,...)you may have to face the so called crossthreading problem. Refer to the

following section to learn about this problem

If you have a long operation to perform and you wish to be able toperform other tasks in the meantime with your program, you can use a

35

Page 37: Introduzione Alla Programmazione Dei SAD

BackgroundWorker. The background worker is a component available inthe toolbar, which provides the capability of spawning a separate threadstill having the possibility to monitor it from your main program. Letget this component and call it backgroundWorkerLong. The backgroundworker supports two events plus another ’special’ event. The two events areProgressChanged, you can use to send feedbacks to your main program keep-ing it informed of the processing advance, and RunWorkerCompleted, whichis raised when your long process ends. The special event is DoWork whichis like a method called to start the process. It is in the form of event justbecause it is used to map your long procedure into the background workerspace.

Now let us to define a long operation:

int longWork(int interval, BackgroundWorker worker,

DoWorkEventArgs e){

//perform a long operation.....

for (long i = 0; i < Int32.MaxValue; i++)

{

;

}

return 0;

}

If this operation were performed inside a button code it would block theinterface unless we used the Application.DoEvents() command. Howeverin this application we prefer to put the code inside the background worker.To do this we create the worker

backgroundWorkerLong.WorkerReportsProgress = true;

backgroundWorkerLong.WorkerSupportsCancellation = true;

int interval=1000000;

backgroundWorkerLong.RunWorkerAsync(interval);

The two initial lines tell the worker you want to be able to cancel it andyou want a feedback. This makes the thread a bit more heavy so the defaultvalues for these properties is false. The method .RunWorkerAsync is thekey point which starts the work. Note that here you do not tell the systemwhich work you want to do (no reference to out ’long procedure’) however

36

Page 38: Introduzione Alla Programmazione Dei SAD

you can pass a parameter of type object (in this example an int, see belowfor a more complex use of objects) to the worker.

The place you specify which work you want the background to execute isthe code of the DoWork event:

private void backgroundWorkerLong_DoWork(object sender,

DoWorkEventArgs e)

{

BackgroundWorker worker = (BackgroundWorker)sender;

// The worker result goes into

// DoWorkEventArgs result

//perform the long operation.....

int interval = (int)e.Argument;

for (long i = 0; i < Int32.MaxValue; i++)

{

if (i % interval == 0)

{

if (worker.CancellationPending)

{

e.Cancel = true;

e.Result = 0;

return;

}

double percentComplete =

((double)i / (double)Int32.MaxValue * 100000000);

worker.ReportProgress((int)percentComplete);

}

}

e.Result = 0;

}

as you can see every interval numbers a ReportProgress event is fired anda check to see if a cancelation request is pending is made. Is such a case theflag of cancel is set at true and the work is interrupted (i.e. the procedurereturns).

37

Page 39: Introduzione Alla Programmazione Dei SAD

Please observe that the _DoWork procedure runs in the separate threadthis allows the long process not to block the other processes, but as antic-ipated you CANNOT access graphic controls by this procedure since the con-trols were created in another thread. Please also observe that the _ProgressChangedand _RunWorkerCompleted procedure CAN access the controls since they arefired by the background thread, but run in the main thread.

Both the ReportProgress and the end of the procedure fire an event:

private void backgroundWorkerLong_ProgressChanged(object sender,

ProgressChangedEventArgs e)

{

float percent = e.ProgressPercentage / 1000000;

this.textBoxReport.Text = percent.ToString();

}

private void backgroundWorkerLong_RunWorkerCompleted(object sender,

RunWorkerCompletedEventArgs e)

{

if (e.Error != null)

{

MessageBox.Show(e.Error.Message);

}

else if (e.Cancelled)

{

this.textBoxReport.Text = "Canceled";

}

else

{

this.textBoxReport.Text = e.Result.ToString();

}

}

If you need to start the thread passing several parameters to it you haveto create a single object that contains all the parameters. The usual way todo this is to use a class that contains everything

....

38

Page 40: Introduzione Alla Programmazione Dei SAD

class multipleParams

{

public int interval = 0;

public String greetings = "Hello";

}

.....

.....

....

multipleParams p= new multipleParams();

p.interval = interval;

p.greetings = "Go!";

backgroundWorkerLong.RunWorkerAsync(p);

Of course inside the background procedure you need to decode the object

private void backgroundWorkerLong_DoWork(object sender,

DoWorkEventArgs e)

{

.....

if (e.Argument is multipleParams)

{

multipleParams p = (multipleParams)e.Argument;

interval = p.interval;

}

else

//deal with other object types;

Eventually please note that as usual the use of the toolbox is only aconvenience; should you prefer writing the code by hand you simply has todeclare the working thread object and link the required procedures to it.Below the code which is automatically generate by the IDE.

private System.ComponentModel.BackgroundWorker backgroundWorkerLong;

39

Page 41: Introduzione Alla Programmazione Dei SAD

.....

this.backgroundWorkerLong = new System.ComponentModel.BackgroundWorker();

....

this.backgroundWorkerLong.DoWork += new

System.ComponentModel.DoWorkEventHandler

(this.backgroundWorkerLong_DoWork);

this.backgroundWorkerLong.RunWorkerCompleted += new

System.ComponentModel.RunWorkerCompletedEventHandler

(this.backgroundWorkerLong_RunWorkerCompleted);

this.backgroundWorkerLong.ProgressChanged += new

System.ComponentModel.ProgressChangedEventHandler

(this.backgroundWorkerLong_ProgressChanged);

5.9 Extending classes with new and override

The complete source code of this example is available in theClassExtention package

A C# class can be extended (i.e. new method added) and modified (i.e.changing the way existing methods work) by using the keywords override

and new virtual in front of method declaration you want to add/change.Extending classes may be a trickly affair so use this option with care. Keep inmind that this section is only a very limited subset of the possible situationsand is meant as an introduction to a way to solve the so called cross-threadingproblem.

A derived class can be obtained from the base class at declaration byadding the base type to the class type. As an example, the following codecan be used to declare a derived class of type SafeTextBox that extend thebase class of type TextBox

....

public class TextBoxExtended : TextBox

{

......

This means that an object of type TextBoxExtended has all properties,method and events of the base class TextBox (e.g. .Text, .AppendText,....)plus all the method, properties, events you add to the new class.

40

Page 42: Introduzione Alla Programmazione Dei SAD

If you type and code a method that has the same name as a methodalready present you are trying to change the way it behaves in your class. Atthis point two possibilities arise:

• if the base method was declared (by the programmer who created thebase class) as virtual, the method can be modified by writing yourmethod with the keyword override in front

• if the base method was not declared (by the programmer who createdthe base class THIS IS THE DEFAULT) as virtual, the keywordoverride cannot be used and you have to declare your method aseither new or new virtual. In the first case you are going to preventother programmer to use override on your new method, while in thesecond case you are going to allow it.

In both cases inside the method you are changing, you can start the originalcode by calling it on the convention object named base

As an example, if the extended class is defined as follows

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows.Forms;

namespace ClassExtention

{

class TextBoxExtended : TextBox

{

public override string Text

{

get

{

return base.Text;

}

set

{

base.Text = ">>>" + value + "<<<";

}

}

41

Page 43: Introduzione Alla Programmazione Dei SAD

public new virtual void AppendText(string aString)

{

base.AppendText("++++" + aString);

}

}

}

the net results is that setting the Text property with the string Hello turnsout in the appearance of the string >>>Hello<<< (not so useful, just to ex-plain....).

As you see the example modifies a property (Text) and a method (AppendText).The former is declared virtual inside the TextBox so that we can mod-ify it via override while the latter is not declared virtual so that thenew virtual approach has to be used.IMPORTANT: if you define the extended class in a separate class file, youget the new object on the toolbox the first time you compile the project.This allows you add your new components as if they were the base ones.This is quite useful in designing your interface.

5.10 Using delegates to deal with the cross-

threading problem

The complete source code of this example is available in theThreadSafeClasses package

This section explain how to access a control from a thread the did nocreated it by means of a delegate either in a procedure coded in the mainprogram or by means of a control that is extended to include the delegateprocedures. This example is an extension of the Dealing with threads

As told in that section the problem is in the _DoWork procedure. If youtry to access a control (e.g. a textbox) from the procedure an error arisetelling you are accessing a control from a thread different from the one thatcreated it.

private void backgroundWorkerLong_DoWork(object sender, DoWorkEventArgs e)

{

42

Page 44: Introduzione Alla Programmazione Dei SAD

......

textBoxNOTWORK.Text=String.Format("{0:###.###}",

percentComplete / 1000000);

.......

To avoid this error you must delegate the access operation to a procedurethat runs in the control thread so as a first operation you need to define theprocedure.

The controls have a specific property named InvokeRequired that checksthe ID of the calling thread and the ID of the thread that created the controland returns true if they are different (and therefore the control cannot bedirectly accessed)

5.10.1 Using a delegate in the program

The delegate approach is accomplished by two different operations. Firstlyyou have to tell the compiler you want to use a delegate procedure and whichare the procedure parameters

delegate void SetTextCallback(TextBox ctl, string text);

Then you have to define a procedure with the same parameters, thateither performs the operation if possible or Invoke a procedure to which itdelegate the work. Here is the code:

private void SetATextCrossThread(TextBox ctl, string text)

{

// InvokeRequired compares the thread ID of the

// calling thread to the thread ID of the creating thread.

// If these threads are different, it returns true.

if (ctl.InvokeRequired)

{

//create the callback object and invoke it

SetTextCallback d =

new SetTextCallback(SetATextCrossThread);

this.Invoke(d, new object[] { ctl, text });

}

else

43

Page 45: Introduzione Alla Programmazione Dei SAD

{

//no invoke required simply append the text

ctl.Text = text;

}

}

At this point if you want to access the control you call the procedure inthe usual way

private void backgroundWorkerLong_DoWork(object sender, DoWorkEventArgs e)

{

.....

SetATextCrossThread(textBoxWork, message);

......

5.10.2 Using a control that is extended to deal withthe delegate approach

This solution require you to extend the control you want to use and use theextended control instead of the base one. The extension has been describedin section Extending classes with new and override

Keep in mind that you need to override/renew only the properties/methodsyou want to use cross-thread and not all the methods. In the example belowonly the text method is changed. Some notes:

1. as you see from the example below, the delegate is defined directlyinside the code

GetText getTextDel = delegate()

{

return base.Text;

};

2. the InvokeRequired here is an unqualified property (i.e. without a dotin front) since we are inside the class

44

Page 46: Introduzione Alla Programmazione Dei SAD

3. the base keyword is used to refer to the base class

4. the #pragma warning disable 1911 directive is used to get rid of awarning that comply about the anonymous method use

5. if the code is inside a class file included in the project the new objectappears in the toolbox and can used as any other base component

Here the complete code

#pragma warning disable 1911

......

{

public class SafeTextBox : TextBox

{

delegate void SetText(string text);

delegate string GetText();

public override string Text

{

get

{

if (InvokeRequired) // Is Invoke required?

{

GetText getTextDel = delegate()

{

return base.Text;

};

string text = String.Empty;

try

{

text = (string)base.Invoke(getTextDel, null);

}

catch

{

}

return text;

45

Page 47: Introduzione Alla Programmazione Dei SAD

}

else

{

return base.Text;

}

}

set

{

if (InvokeRequired) // Is Invoke required?

{

SetText setTextDel = delegate(string text)

{

base.Text = text;

};

try

{

base.Invoke(setTextDel, new object[] { value });

}

catch

{

}

}

else

base.Text = value;

}

}

}

and here the use of the code which is exactly as a conventional textbox

private void backgroundWorkerLong_DoWork(object sender, DoWorkEventArgs e)

{

......

safeTextBoxWorks.Text = message;

......

46

Page 48: Introduzione Alla Programmazione Dei SAD

The project that explain this section contains a bunch of extended con-trols you can use in your projects and that are thread aware.

5.11 Revision questions

• How can you format two numbers like 1.333333333 and 5.0 so that theyare printed as 1.33 and 0005 ?

• How can you be sure that regardless of the computer operating systemlanguage a fractional number is printed using the dot as the separatorbetween integer and fractional part?

• How can you be sure that regardless of the computer operating systemlanguage a fractional number is correctly interpreted?

• How can you measure a time interval and what is the nominal resolu-tion?

• How can you efficiently copy the content of an array to another array?

• How can you open a file and write numerical data to it so that the datacan be read with a text editor such as notepad?

• How can you open a file and write numerical data to it in binary mode?

• How can you open a file and efficiently read/write large blocks of nu-merical data to/from it in binary mode?

• How can you have a window message appearing that require a userintervention?

• How can you easily obtain maximum and minimum of an array?

• How can you prevent an execution error to crash the program?

• What is the difference between catch and finally?

• What is a background thread, when it is useful?

• How a background thread can communicate with the calling thread?

• How a background thread can be interrupted?

47

Page 49: Introduzione Alla Programmazione Dei SAD

• How can you pass several parameters to a thread?

• How can you create a background thread without using the toolbox?

• How can you extend a class and why this is useful?

• What is a virtual method, when you have to used the keyword override?

• What is the cross-thread problem? when it arises? How can you getrid of this problem?

• What are delegates?

• What does it means the keyword InvokeRequired?

48

Page 50: Introduzione Alla Programmazione Dei SAD

Chapter 6

Drawing a signal evolution

6.1 General considerations

This is a very common task in the instrumentation and measurement field.Each time you have a phenomenon which evolves in time you may have thedesire to see how the signal evolves. From a mathematic point of view theproblem can be expressed with a time depending function:

V = f(t) (6.1)

where f is a generic function and V is the quantity you ar interested in.The solution to this request is of course well known and represented by acartesian plot where the abscissa is the time and the ordinate is the quantityhere denoted by the letter V , as if it were a voltage.

To fix the ideas let us supposed we are interested in following the theprocess of a capacitor discharge as highlighted in fig 6.1

From the theory it is well known that, after the switch is opened at timet0, the voltage across the capacitor follows an exponential decay.

V = V0et−T0RC (6.2)

The result for a t0 = 1s, V0 = 1V, and τ = RC = 1s is shown in fig 6.2However, you do not have such function in the reality; the only thing

you can do is to put a voltage sampler across the capacitor and sample thevoltage as it decrease. The result of this operation is a series of voltages youcan arrange in vector of voltage samples. Of course you have to know theinterval between the samples i.e. the sampling interval δt. At this point we

49

Page 51: Introduzione Alla Programmazione Dei SAD

Figure 6.1: The RC circuit for the discharge example

Figure 6.2: The voltage evolution for the RC discharge

can prepare a cartesian plot by reporting the voltage samples on the paperobtaining something similar to 6.3 where the parameters are the same asbefore and the sampling interval has been set to 0.5s

Of course you can connect the points with lines to obtain a better repre-sentation of the discharge. This way you could obtain a plot like 6.4

In the following chapters you will learn how to obtain the plot reported in6.4 directly in the acquisition program, but it can be interesting to observehow the plots above have been obtained.

The first consideration regards the plot of fig 6.2 which shows the ’func-tion’ behavior. Of course, since the plot has been obtained electronically, theonly way to obtain the trace is to construct it by points and therefore fig. 6.2

50

Page 52: Introduzione Alla Programmazione Dei SAD

Figure 6.3: The voltage evolution for the RC discharge

Figure 6.4: The voltage evolution for the RC discharge

and fig. 6.4 are conceptually the same thing, simply with a different numberof points: fig 6.2 has so many short lines that it appears as a continuousline. All three plots can be easily obtained by using programs for data ma-nipulation such as Matlab c©or Scilab c©Scilab is an OpenSource equivalentof Matlab which can be freely downloaded at http://www.scilab.org andgives almost the same functionalities at no cost. In the remaining part ofthis document all the examples requiring data manipulation are carried outusing Scilab.

The most important section which create the traces is the following

deltaT=0.01; //use a small sampling interval to have a smooth line

51

Page 53: Introduzione Alla Programmazione Dei SAD

t=[0:deltaT:6];

t_0=1;

tau=1;

V_0=5;

V=V_0*ones(1,size(t,2));

openId=find(t==t_0);

discharge=[openId:size(t,2)];

V(discharge)=V_0*exp(-(t(discharge)-t_0)/tau);

plot(t,V); //plot as a continous line

As you can see the data creation ends with two vectors t and V of iden-tical length which are then plotted on a cartesian space with the commandplot(t,V);. A couple of considerations:

• During a real measurement it is expected that the vector V is producedby real measurements, while the vector t is to be created either taggingeach element of V with the actual acquisition time or created a prioriifthe acquisition interval is predefined.

• the command plot(t,V); automatically scale the traces so that theyfill the plot window. If you do not use programs like Scilab, you haveto scale the data yourself

The complete code which has been used for generating the figures in thistutorial is reported below1 . After the code has run in Scilab, each figurehas been copied to the clipboard and pasted into an OpenOffice draw to beexported in jpeg format and imported in Latex.

xdel(winsid()); //equivalent to matlab close all

//first plot: the continuous line

deltaT=0.01; //use a small sampling interval to have a smooth line

t=[0:deltaT:6];

t_0=1;

tau=1;

V_0=5;

V=V_0*ones(1,size(t,2));

1This code in a file named RCDischarge.sce available within DrawExam-ple.Csharp package

52

Page 54: Introduzione Alla Programmazione Dei SAD

openId=find(t==t_0);

discharge=[openId:size(t,2)];

V(discharge)=V_0*exp(-(t(discharge)-t_0)/tau);

plot(t,V); //plot as a continuous line

//second plot the sampled points

scf(); //equivalent to matlab figure

deltaT=1; //use a large sampling interval to emulate the measurement

t=[0:deltaT:6];

t_0=1;

tau=1;

V_0=5;

V=V_0*ones(1,size(t,2));

openId=find(t==t_0);

discharge=[openId:size(t,2)];

V(discharge)=V_0*exp(-(t(discharge)-t_0)/tau);

plot(t,V,’*’); //plot only the measured points

//third plot: the sampled interpolated points

scf();

plot(t,V); //plot an interpolation

//the plots have small fonts and thin lines good for making a visual study

//but not so nice to be inserted into a document.

//We have to adjust the figure properties to obtain nice plots

figH=winsid(); //enumerate the figures

for i=1:size(figH,2) //for each figure

scf(figH(i)); //select the figure

m=get("current_figure"); //get the figure handle

//a simple figure has one child which is the axis

ax=m.children;

ax.font_size=6; //increase the font

//now change color an thickness of the traces

53

Page 55: Introduzione Alla Programmazione Dei SAD

//the axis has a children compound which contains the traces

compound=ax.children;

polyline=compound.children; //the children of the compound is the polyline

polyline.thickness=2; //increase the line width

polyline.foreground=0; //use color black for lines

polyline.mark_foreground=0; //use color black for points

end

For people wishing to spend money, here is the equivalent Matlab script2

%matlab script for generating data similar to the ones

clear all

%first plot: the continuous line

deltaT=0.01; %use a small sampling interval to have a smooth line

t=[0:deltaT:6];

t_0=1;

tau=1;

V_0=5;

V=V_0*ones(1,size(t,2));

openId=find(t==t_0);

discharge=[openId:size(t,2)];

V(discharge)=V_0*exp(-(t(discharge)-t_0)/tau);

plot(t,V); %plot as a continous line

%second plot the sampled points

figure(2);

deltaT=1; %use a large sampling interval to emulate the measurement

t=[0:deltaT:6];

t_0=1;

tau=1;

V_0=5;

V=V_0*ones(1,size(t,2));

openId=find(t==t_0);

discharge=[openId:size(t,2)];

V(discharge)=V_0*exp(-(t(discharge)-t_0)/tau);

2This code in a file named RCDisc.m available within DrawExample.Csharp pack-age

54

Page 56: Introduzione Alla Programmazione Dei SAD

plot(t,V,’*’); %plot only the measured points

%third plot: the sampled interpolated points

figure(3);

plot(t,V); %plot an interpolation

6.2 Doing it in C#: the simple way

The complete source code of this example is available in theDrawExample.Csharp package

Let us start from scratch: open the Visual Studio IDE and create a newsolution of type Windows Form, in order to be compatible with the exampleuse DrawExample.Csharp as solution name

In this example we only need a picture box and a button; the picture boxwill be the area on which the plot appears, while the button will be used totrigger the trace creation. You are strongly advised to change the defaultnames of the object so that they remember you of their function. In theexample we can use buttonDraw and pictureBoxTrace. You can change thenames by using the property panel on the right. With the same panel youcan also change the button text to Draw! and add a border to the pictureBoxso that it appears clearly on the form (Fig. 6.5).

Figure 6.5: The form for the draw example with button and pictureBox

At this point we need to write two pieces of code:

55

Page 57: Introduzione Alla Programmazione Dei SAD

• the code fill the vectors to draw and, since we do not have anyonescaling the data for us, we also have to decide how to scale the data inthe pictureBox.

• the code that actually paint the trace

Since the code that paints the traces is in a procedure different from theone that fill the trace, we also need to declare the vectors which hold thetrace as global. To do this simply double click the form and go at top todeclare the vectors:

....

public partial class DrawExampleForm : Form

{

float[] t, V;

RectangleF picScale;

public DrawExampleForm()

{

.......

The code for generating the data can be written in the procedure con-nected to the click event for the buttonDraw object. You can access theprocedure by double clicking the button.

.......

private void buttonDraw_Click(object sender, EventArgs e)

{

float tMax=6.0F; //the number of seconds we want to plot

float deltaT=1.0F; //use a large sampling

//interval to emulate the measurement

int nMax=(int)(tMax/deltaT)+1; //the number of samples we need;

t=new float[nMax]; //allocate the memory to hold the samples

V=new float[nMax];

float t_0=1; //the switch opening time

float tau=1; //the time constant

56

Page 58: Introduzione Alla Programmazione Dei SAD

float V_0=5; //the initial voltage

//generate the t vector

for (int i=0;i<nMax;i++)

t[i]=i*deltaT;

//compute the values for the discharge

for (int i=0;i<nMax;i++){

if (t[i]<=t_0)

V[i]=V_0;

else

V[i]=V_0*(float)Math.Exp(-(t[i]-t_0)/tau);

}

//set a dimension to draw

//in this case we want a rectangle starting at x=0 y=0

float xStart = 0;

float yStart = 0;

//... and with dimension xsize=tMax ysize=V_0

float xSize = tMax;

float ySize = V_0;

picScale = new RectangleF(xStart, yStart, xSize, ySize);

//invalidate the picture to force the refresh

pictureBoxTrace.Invalidate();

}

.........

Some remarks on the code:

• t=new float[nMax]; the memory allocation in C# is performed throughthe new keyword and therefore keywords like malloc and free are notpresent3. There is no requirement to deallocate no longer used vectorssince the system automatically manages the memory4

3Actually it is possible to use these keywords and the pointers only within the un-safeenvironment

4This is not really true, sometime, with very large vectors, an explicit memory free .i.ean assignment to nullfollowed by an explicit call to the garbage collector GC.Collect();canbe useful to reduce the memory allocation

57

Page 59: Introduzione Alla Programmazione Dei SAD

• float tMax=6.0F; the final F is required since a a number is by defaultrepresented as a double so that an explicit downcast is required

• (float)Math.Exp(-(t[i]-t_0)/tau) All mathematical function in C#are accessed through the Math object i.e. you cannot write x=sin(y)

since there is not a sin function in C#; you must write y=Math.Sin(x).The Math object returns a double so also in this case an explicit down-cast is required to assign the value to a float variable

Now we have to write the code that actually paints the trace. The codemust be executed in response to a paint event. To access the procedureconnected to the paint event select the pictureBox and in the propertieshighlight the event by using the specific button. Among the events goon paint and click it. The IDE automatically creates a procedure namedpictureBoxTrace_Paint and links it to the paint events (you can see thislink by opening the file DrawExampleFormDesigner.cs which is present inthe solution explorer on the right. Expanding the section

Windows Form Designer generated code

you will find a line saying

this.pictureBoxTrace.Paint +=

new System.Windows.Forms.PaintEventHandler(this.pictureBoxTrace_Paint);

Take note of this line since we will use this syntax again the following). Atthis point you have an empty procedure:

private void pictureBoxTrace_Paint(object sender, PaintEventArgs e)

{

}

where sender is the PictureBox whose are you want to paint and e is theparameter which allows you to access the graphic engine.

The painting involves two operations: data scaling, to have your tracefilling the area defined by the picScale rectangle, and the actual painting.The scaling can be accomplished by using the transform capabilities of thegraphic engine asking it to map the physical picture box dimension (in pixels)to your time/voltage values.

58

Page 60: Introduzione Alla Programmazione Dei SAD

private void pictureBoxTrace_Paint(object sender, PaintEventArgs e)

{

//to avoid errors skip painting if the requested scale is silly

if (picScale.Width == 0 | picScale.Height == 0)

return;

RectangleF clipB;

//this is the picture box we are working on

PictureBox pb = (PictureBox)sender;

//this is the graphic object associated to the box

Graphics g = e.Graphics;

//apply the scale transformations

//which are required to draw the vectors as we want

g.ResetTransform();

clipB = pb.ClientRectangle; //get the actual pic size

//adjust x and y scales remembering

//that pixels are counted up/down while

//the normal Y positive axis is upward

//apply a scale transform

g.ScaleTransform(clipB.Width / picScale.Width,

-clipB.Height / picScale.Height);

//move the origin so that the lower left corner

//is where we want

g.TranslateTransform(-picScale.X, -picScale.Y - picScale.Height);

.........

}

}

Eventually we can perform the painting by taking advantage of one oftwo different methods of the graphic engine: DrawLine and DrawLines. Bothrequire you to define a Pen whose properties are used to paint. The Pen hasseveral attributes, but the most important are the color and the thickness.A zero value for the thickness means a line of one pixel and we will use suchvalue. Pay attention that the thickness value is expressed in user space valuesand this can generate surprising effects. In addition, using a value differentfrom zero can greatly slow down the drawing so that use this opportunitywith care. An alternative solution which can be used if you really need a

59

Page 61: Introduzione Alla Programmazione Dei SAD

thick pen is described in the section of resizable buffered drawing.

float penThickness = 0;

Pen redPen = new Pen(Color.Red, penThickness);

The DrawLine version of the painting is quire straightforward;

.......

int iMax = V.Length - 1;

for (int i = 0; i < iMax ; i++)

{

g.DrawLine(redPen,

t[i], V[i],

t[i + 1], V[i + 1]);

}

}

}

The DrawLine based solution has one advantage and two disadvantagesover the DrawLines one. The disadvantages are

1. if two points are close together they are NOT DRAWN at all (bug!)

2. the code is not as efficient as using DrawLines

while the advantage is that it does not require additional memory allocationThe DrawLines solution requires5 the allocation of a vector of PointF

PointF[] pf = new PointF[iMax];

for (int i = 0; i < iMax; i++)

pf[i] = new PointF(t[i], V[i]);

//draw the polyline

g.DrawLines(redPen, pf);

The result of our work is in Fig. 6.6

5Please note that it would also be possible to use such structure instead of the floatvectors for t and V thus avoiding the double allocation, but this would change the similaritywith the Scilab example

60

Page 62: Introduzione Alla Programmazione Dei SAD

Figure 6.6: The plotted signal

This way to plot data works, but it has a little problem: each time theyour window needs to be repainted, maybe because you cover it with anotherwindow, the entire drawing process is required even though no change in thedata occurs. To highlight the effect of this operation let us to change the line

float deltaT=1.0F;

to

float deltaT=0.000001F;

Now with the parameters we have, the number of samples grows to sixmillions so the plot time becomes not negligible. You can see what happensif you cover your form with another window and then you uncover it. As youcan see the windows seems freezing for a certain time. In the next section wewill see how to avoid this problem at the expense of a little more complexcode.

6.3 Doing it in C#: using a double buffer

The complete source code of this example is available in theDrawExampleBuffer.Csharp package

As you have experienced in the last test the problem in our code is thatthe paint event requires a recreation of the entire trace even though it is

61

Page 63: Introduzione Alla Programmazione Dei SAD

unchanged. The solution is to use a double buffer, write the trace onto thebuffer, which will hold an image of the trace and then simply render theimage each time we need so.

Let us start the same way as the previous example, this time naming itDrawExampleBuffer. Now we need to declare the variables for holding thebuffer, which have to be global since they have to be accessible from differentprocedures.

public partial class DrawExampleBuffer : Form

{

BufferedGraphicsContext context;

BufferedGraphics grafx;

float[] t, V;

RectangleF picScale;

....

we need BufferedGraphicsContext which provide the space for holding thebuffer and a BufferedGraphics which provide the bridge to the graphicengine and the buffer

Then when we want to plot the image we have to allocate the buffer anddraw inside it instead that writing on the PictureBox. Both operations canbe performed when we press the Draw button which this way will containthe code that originally was in the paint event.

private void buttonDraw_Click(object sender, EventArgs e)

{

...........

//up to here same as before......

//retrieve a context

context = BufferedGraphicsManager.Current;

//allocate the memory

context.MaximumBuffer = new Size(this.pictureBoxTrace.Width + 1,

this.pictureBoxTrace.Height + 1);

grafx = context.Allocate(this.pictureBoxTrace.CreateGraphics(),

new Rectangle(0, 0, this.pictureBoxTrace.Width,

this.pictureBoxTrace.Height));

62

Page 64: Introduzione Alla Programmazione Dei SAD

Graphics g;

g = grafx.Graphics;

//now the code that originally was in the paint event

g.ResetTransform();

Rectangle clipB = pictureBoxTrace.ClientRectangle;

g.FillRectangle(Brushes.Azure, clipB);

g.ScaleTransform(clipB.Width / picScale.Width,

-clipB.Height / picScale.Height);

g.TranslateTransform(-picScale.X, -picScale.Y - picScale.Height);

Pen redPen = new Pen(Color.Red, 0);

//allocate the PointF[] structure and fill it

int nMax = V.Length - 1;

PointF[] pf = new PointF[nMax];

for (int i = 0; i < nMax; i++)

pf[i] = new PointF(t[i], V[i]);

//draw the polyline

g.DrawLines(redPen, pf);

pictureBoxTrace.Invalidate();

}

Eventually the code in the Paint event becomes a simple request to renderthe buffer

private void pictureBoxTrace_Paint(object sender, PaintEventArgs e)

{

//before we push the draw button

//the grafx buffer does not exist!

if (grafx!=null)

grafx.Render(e.Graphics);

}

Try the code: the first time you press the draw button you see the delayrequired to create the image, but after that no other delays are experiencedif you cover/uncover the form.

63

Page 65: Introduzione Alla Programmazione Dei SAD

6.4 Doing it in C#: handling the form resize

and using thick pens

The complete source code of this example is available in theDrawExampleBufferResize.Csharp package

We are nearly to the end of this example, however plots obtained in Scilabare resizable so that we can adapt the from the our screen. This can be easilydone with little effort. Let us start as in previous examples, then selectingthe form go to properties and double click the ResizeEnd event so that theprocedure for this event appears. You could also select the Resize event,but this would trigger the paint event several times during the resize slowingdown the interface.

private void FormDrawExampleBufferResize1_ResizeEnd

(object sender, EventArgs e)

{

}

Now write the code to resize the PictureBox as an example by leavingthe upper left corner fixed and changing the lower right corner to be justneat to the form limit.

private void FormDrawExampleBufferResize1_ResizeEnd

(object sender, EventArgs e)

{

Rectangle rf = this.ClientRectangle;

int xPictureBox = rf.Width - pictureBoxTrace.Left - 10;

int yPictureBox = rf.Height - pictureBoxTrace.Top - 10;

if (xPictureBox > 0 & yPictureBox > 0)

{

pictureBoxTrace.Size =

new Size(xPictureBox, yPictureBox);

}

}

If you try changing the from size you will se the picturebox size changingas required.

64

Page 66: Introduzione Alla Programmazione Dei SAD

Now we can re-use the code already written for creating the data, butthis time we have to change the code which allocate the memory in theBufferContext that must change each time the form is resized. To do thiswe have to manage the Resize event of the PictureBoxTrace

private void pictureBoxTrace_Resize(object sender, EventArgs e)

{

allocateContext();

doRedraw();

}

private void allocateContext()

{

//retrieve a context

if (context == null)

context = BufferedGraphicsManager.Current;

//allocate the memory

context.MaximumBuffer =

new Size(this.pictureBoxTrace.Width + 1,

this.pictureBoxTrace.Height + 1);

grafx = context.Allocate(this.pictureBoxTrace.CreateGraphics(),

new Rectangle(0, 0, this.pictureBoxTrace.Width,

this.pictureBoxTrace.Height));

}

as you can see we have now a procedure called allocateContext whichis called each time the picturebox is resized so that the memory allocationfollows the picture size. As you see the paint event also call another procedurecalled doRedraw which performs the redraw each time the picture is resized.Such procedure contains the lines that draw into the buffer and that werepreviously contained inside the draw procedure

private void doRedraw()

{

//allocate the PointF[] structure and fill it

DateTime startTime = DateTime.Now;

65

Page 67: Introduzione Alla Programmazione Dei SAD

int nMax = V.Length - 1;

PointF[] pf = new PointF[nMax];

for (int i = 0; i < nMax; i++)

{

pf[i] = new PointF(t[i], V[i]);

}

//define the pen to be used: be warned that the pen width is in

//scaled units!

CultureInfo cEn = new CultureInfo("en-US");

float width = (float)Double.Parse(this.textBoxWidth.Text, cEn);

Pen redPen = new Pen(Color.Red, width);

Graphics g;

g = grafx.Graphics;

g.ResetTransform();

if (picScale == null | V == null | t == null)

return;

Rectangle clipB = pictureBoxTrace.ClientRectangle;

g.FillRectangle(Brushes.Azure, clipB);

//use the transform capabilities of the graphics object

g.ScaleTransform(clipB.Width / picScale.Width,

-clipB.Height / picScale.Height);

g.TranslateTransform(-picScale.X, -picScale.Y - picScale.Height);

//draw the polyline

g.DrawLines(redPen, pf);

DateTime stopTime = DateTime.Now;

TimeSpan elapsedTime = stopTime - startTime;

this.textBoxTime.Text = String.Format("{0:000.000 ms}",

elapsedTime.Ticks * 1e-4);

pictureBoxTrace.Invalidate();

}

private void buttonDraw_Click(object sender, EventArgs e)

66

Page 68: Introduzione Alla Programmazione Dei SAD

{

..... same as before

allocateContext();

doRedraw();

}

As already said, since the pen thickness is different from zero the valuethickness is interpreted in user coordinates and this can produce surprisingresults especially when the horizontal and vertical scales are numericallyquite different. You may experiment what is happening by changing the penthickness in the form and redrawing the signal.

If you need to use pens with a thickness different from zero is better toavoid using the transform facility of the graphics object. In this case the scal-ing of the values to be displayed can be accomplished by using the capabilitiesof the Affine transform as provided by the Matrix object. The Matrix ob-ject has Scale and Translate methods that operate in the same way theScaleTransform and TranslateTransform for the Graphics object. Oncethe matrix is initialized, the method .TransformPoints(<point vector>)

can be called that operate the data transform in the new coordinate system.Pay attention that the method overwrite the original values contained in thevector.

private void doRedrawByAffineTransform()

{

if (picScale == null | V == null | t == null)

return;

DateTime startTime = DateTime.Now;

//allocate the PointF[] structure and fill it

int nMax = V.Length - 1;

PointF[] pf = new PointF[nMax];

for (int i = 0; i < nMax; i++)

{

pf[i] = new PointF(t[i], V[i]);

}

//allocate a matrix to perform the affine transform

Matrix tr = new Matrix();

67

Page 69: Introduzione Alla Programmazione Dei SAD

Rectangle clipB = pictureBoxTrace.ClientRectangle;

tr.Scale(clipB.Width / picScale.Width,

-clipB.Height / picScale.Height);

tr.Translate(-picScale.X, -picScale.Y - picScale.Height);

//tranform the points

//BEWARE: the transformed points are in the SAME vector!

//the original values are lost!

tr.TransformPoints(pf);

//define the pen to be used: be warned that the pen width is in

//scaled units, but this is not a problem in this case

CultureInfo cEn = new CultureInfo("en-US");

float width = (float)Double.Parse(this.textBoxWidth.Text, cEn);

Pen bluePen = new Pen(Color.Blue, width);

Graphics g;

g = grafx.Graphics;

g.ResetTransform();

g.FillRectangle(Brushes.Azure, clipB);

//draw the polyline without using the graphic object scaling

g.DrawLines(bluePen, pf);

DateTime stopTime = DateTime.Now;

TimeSpan elapsedTime = stopTime - startTime;

this.textBoxTime.Text = String.Format("{0:000.000 ms}",

elapsedTime.Ticks*1e-4);

pictureBoxTrace.Invalidate();

}

Also note that this solution is less efficient than using the transform ca-pability of the Graphics object. The penalty varies with the conditions, butyou can expect a redraw 7-8 times slower by using the affine transform anda pen thickness of zero. The penalty reduces to 2-3 times when using a penthickness different from zero.

68

Page 70: Introduzione Alla Programmazione Dei SAD

6.5 Revision questions

• Which object is useful for drawing a signal evolution in C#?

• What is the Paint event and how you have to deal with it?

• How can you define the virtual dimension (i.e. the scale) of a plottedtrace?

• What is the double buffer and what are the advantages of its use?

• How can you handle the form resizing?

• What are the problems connected to thick (grater then 1 pixel) tracesand how can you deal with this problem?

69

Page 71: Introduzione Alla Programmazione Dei SAD

Chapter 7

Using the Serial line

7.1 Generalities

The serial line is one of the cheapest interface which can be used to connectand control an instrument. Unfortunately the serial line also known as RS232has been invented for a connection between a computer and a modem andnot for a direct connection between two computers (the instrument fromthis point of view can be regarded as a computer). This is not a really bigproblem, but has significant consequences on the way the connection cablebetween instrument and computer has to be arranged. You have to consultthe instrument manual to prepare the correct cable and keep in mind thatthe cable type also affect the code you have to write.

Some notes:

• There are two ways you can follow to add the serial line functionalitiesto your code. You can either take advantage of the IDE toolbox or writeall the serial code yourself starting from scratch. The first approach iseasier to follow, but since the second one will be mandatory for devicesnot appearing in the toolbox, we will explore both approaches. Thefinal result is obviously identical but you are strongly advised to avoidmixing the two approaches (this is not forbidden but if you do not knowexactly what you are doing can become crazy trying to understand whatis happening).

• regardless of the method to add the serial line, the characters comingfrom the serial line are managed by a specific thread so that there

70

Page 72: Introduzione Alla Programmazione Dei SAD

is a real chance you encounter the cross-threading problem as de-scribed in Using delegates to deal with cross-thread. In the follow-ing the solution based on the IDE shows the use of the (deprecated!)CheckForIllegalCrossThreadCalls = false; directive; while the man-ual solution implements the (strongly encouraged!) use of delegates

7.2 Using the serial line taking advantage of

the IDE toolbox

The complete source code of this example is available in theSerialLineUse.Csharp package

This approach can be followed for using components which are present inthe toolbox bar. Please note that we have been using this approach for allthe components we used so far (Buttons, PictureBox, ....). Simply start theIDE, create a new Project and rename the Form as usual. Now go to theToolbox scroll it down until the component SerialPort appears and drag itonto your Form (fig. 7.1.

Differently that with other visual components (e.g. Buttons), the newobject is placed at the bottom in a separate strip. You are strongly advisedto change the name of the newly created component as usual, after that youcan start using it as usual and you can take advantage of the automaticcreation of procedures connected to the events (fig. 7.2.

Now we can proceed as usual adding a Button to open/close the line, abutton and a textbox to send a message onto the line and a TextBox to seewhat is received from the line (fig. 7.3.

In order to use the features of the serial port we have to import theport definitions and since we have are going to use a communication withouthandshake (see below) we also have to import the Threading definition inorder to be able to pause the program.

using System.IO.Ports;

using System.Threading;

Before entering the code discussion please observe the code in the loadevent which says

71

Page 73: Introduzione Alla Programmazione Dei SAD

Figure 7.1: Dragging the serial port object onto the form

private void FormSerialLIne_Load(object sender, EventArgs e)

{

//at startup the line is close so disable the send dutton

this.buttonSend.Enabled = false;

//this to avoid problems with threading error messages

CheckForIllegalCrossThreadCalls = false;

}

The first statement is trivial, but the second is quite more tricky. For now donot worry about this, we will discuss the cross-thread problem later. Simplykeep in mind that disabling the check as we are doing here is a dangerousshortcut.

Now we can discuss the port set-up which is contained in the ButtonOpenClosecode

72

Page 74: Introduzione Alla Programmazione Dei SAD

Figure 7.2: The automatic event management provided by the IDE

private void buttonOpenClose_Click(object sender, EventArgs e)

{

if (!serialPortInstrument.IsOpen)

{

serialPortInstrument.PortName = "COM1";

serialPortInstrument.BaudRate = 9600;

serialPortInstrument.Parity = Parity.None;

serialPortInstrument.StopBits = StopBits.One;

serialPortInstrument.DataBits = 8;

serialPortInstrument.DtrEnable = true;

serialPortInstrument.Handshake = Handshake.None;

//serialPortInstrument.Handshake = Handshake.RequestToSend;

//serialPortInstrument.Handshake = Handshake.XOnXOff;

73

Page 75: Introduzione Alla Programmazione Dei SAD

Figure 7.3: Serial port management example

serialPortInstrument.ReceivedBytesThreshold = 1;

serialPortInstrument.Open();

this.buttonSend.Enabled = true;

this.buttonOpenClose.Text = "Close";

}

else

{

serialPortInstrument.Close();

this.buttonSend.Enabled = false;

this.buttonOpenClose.Text = "Open";

}

}

74

Page 76: Introduzione Alla Programmazione Dei SAD

The first time the button is pressed the serial port is closed so that we candefine its properties. The first five lines define the format of the data to beinterchanged and must be updated according to the instrument requirements.In this case we use eight bits, one stop bit an no parity defining a characterof ten bits (including the start bit). This is quite a common set-up; the speedis set to 9600 baud.

The following two lines are related to the kind of protocol (and cable!) weare using. The data flow synchronization can be either hardware, softwareor disabled.

• The hardware handshake Handshake.RequestToSend uses the RTS (re-quest to send) signal to notify data has to be sent and check for CTS(clear to send) signal before sending the data. On course this solutionrequires a cable designed to use such signals (i.e. a cable with at leastfive wires).

• The software handshake Handshake.XOnXOff makes use of the specialcharacters Xon Xoff and does not require control wires in the cable sothat only three wires (ground transmit and receive) has to be present

• When the handshake is disabled (Handshake.None) both the computerand the instrument have to be able to receive the longest message tobe exchanged without suffering from buffer overflow. In addition caremust be taken in order to avoid sending messages too fast i.e. sending amessage before the previous message has been processed by the receiver.In the past, when the processors had limited power, the non handshakeapproach was quite dangerous, nowadays this is a less critical issue.

Eventually the line serialPortInstrument.ReceivedBytesThreshold = 1;

instructs the port driver to generate a DataReceived event when one oremore characters are received. The other lines open the port and enable thesend button since the port is now ready.

At this point we are ready to send messages to the instrument. Makingreference to the instrument we are going to use for our tests, we have to sendtwo messages :SYST:REM and *IDN?. The first message instructs the instru-ment to go in the remote state while the second one requires the instrumentto send back a string with its identification code. The code that accomplishthese operations is

75

Page 77: Introduzione Alla Programmazione Dei SAD

private void buttonSend_Click(object sender, EventArgs e)

{

//put the dmm in remote and ask the IDN

textBoxMessage.AppendText("Sending SYST:REM\r\n");

serialPortInstrument.Write(":SYST:REM\r\n");

Application.DoEvents();

Thread.Sleep(1000);

textBoxMessage.AppendText("Sending *IDN?\r\n");

serialPortInstrument.Write("*IDN?\r\n");

}

A couple of comments:

• each message is terminated with the sequence carriage return line feed.The line feed is mandatory since the instrument complies with theIEEE488.2 protocol which requires ending each command with the linefeed

• The command which puts the instrument in a remote state requires anot negligible time to be executed. Since we are not using an hand-shake, if we sent the second message in sequence (i.e. at the com-puter speed) it would produce an error. the Thread.Sleep(1000);

statement instructs the program to sleep for 1000ms before contin-uing; this is quite a long time but useful to follow the communica-tion sequence, the time can be shortened in real applications. TheApplication.DoEvents(); statement tells the thread which executesthe button statements to yield giving the thread which update the textbox the possibility to update the box. In the absence of such a state-ment the text box would be updated only at the end of the button codei.e. the two lines would appear together.

At this point we have to discuss the most complex part of out work i.e.how to receive the data from the serial line. This operation is not so easydue to the following combination of facts:

• the data arrive asynchronously i.e. we do not know when they arrive;it depends on the instrument speed so that we cannot think of waitinga certain time to be sure all the data have arrived

76

Page 78: Introduzione Alla Programmazione Dei SAD

• the number of characters we have to receive is generally not know inadvance so that we cannot simply instruct the driver to trigger an eventwhen a specified number of characters have arrived

• the data arrive slowly with respect to the computer speed i.e. at 9600baud a character is sent in about 1ms while a computer instruction isexecuted in nanoseconds. In addition the characters can arrive discon-tinuously so that we cannot think of waiting a certain time to be surethe message is complete

The solution to all these constraints is to allocate a global buffer andqueue the characters as they arrive until the terminator character (i.e. thelinefeed for the IEEE499.2 protocol). Only when this happens we can extractthe message from the queue and process it.

The code that performs these operation requires the global definition

public partial class FormSerialLIne : Form

{

String globalReceivedChars;

.....

The data managing is performed by the procedure associated to theDataReceived event (which is triggered each time at least one characterhas arrived). Please not that, since the computer can react with delay to thetrigger event due to the other operation it could be engaged in, there is noguarantee the drive buffer contains on a character.

private void serialPortInstrument_DataReceived(object sender,

SerialDataReceivedEventArgs e)

{

//manage received data

int lfPosition;

String oneLine;

if (e.EventType == SerialData.Chars)

{

globalReceivedChars = globalReceivedChars +

serialPortInstrument.ReadExisting();

//check is a delimiter i.e. a LF has arrived

77

Page 79: Introduzione Alla Programmazione Dei SAD

lfPosition = globalReceivedChars.IndexOf("\n");

if (lfPosition > 0)

{

//extract the message

oneLine = globalReceivedChars.Substring(0, lfPosition);

//process the message

processThisLine(oneLine);

//’remove the message from the buffer

globalReceivedChars =

globalReceivedChars.Substring(lfPosition + 1);

}

}

}

private void processThisLine(String line)

{

//for now do nothing but show the answer

textBoxMessage.Text = textBoxMessage.Text +

">> " + line + "\n";

}

}

As you can see each time the procedure is called all the characters in thedriver buffer are read and queued to the global buffer, the buffer is examinedand if the linefeed is present the message is extracted processed and removedfrom the buffer. At this point the processing is simply an update of the textbox to see what arrived, but also this simple operation is critical.

To see why, try commenting out the line

CheckForIllegalCrossThreadCalls = false;

and look what happens. The reason for the error is that the event drivenprocedure runs in a thread different from the thread that update the graphicinterface so that there is (a not so remote...) possibility that both threads tryto access the text box control at the same time with unpredictable results.

The solution to this problems is that the event driven thread asks themain thread to perform the update with a specific command we will see inthe next paragraph.

78

Page 80: Introduzione Alla Programmazione Dei SAD

7.3 Manual serial line addition and cross thread

solution

The complete source code of this example is available in theSerialLineUseManual package

In this section we will re-create the serial line software without takingadvantage of the toolbox and we will tackle the cross threading problem. Letus start a new project and put Buttons and text boxes as in the previoussection. Also import threading and port definitions since we are going to usethem as before. At this point, instead of dragging the serial line from thetoolbox we explicitly declare the serial port as a global variable

public partial class SerialLineUseManual : Form

{

SerialPort serialPortInstrument;

String globalReceivedChars;

........

The object of type SerialPort is what we need and is defined in theSystem.IO.Ports package. Remember that declaring an object is not creat-ing it and we need to explicitly create the serial port. We can do this in theopen/close button:

private void buttonOpenClose_Click(object sender, EventArgs e)

{

if (serialPortInstrument == null)

{

serialPortInstrument = new SerialPort();

serialPortInstrument.DataReceived +=

new System.IO.Ports.SerialDataReceivedEventHandler

(this.serialPortInstrument_DataReceived);

}

serialPortInstrument.PortName = "COM1";

......

This code fragment makes two things: creates the object serial port (witha check to avoid multiple creations) and associates a procedure

79

Page 81: Introduzione Alla Programmazione Dei SAD

(serialPortInstrument_DataReceived) the the event DataReceived. Suchan association in the previous example was automatically performed by theIDE, but now we have to do it by hand. That is all: the remaining part ofthe code that manages the serial line does not change.

We still have the problem of the cross threading which is a bit more com-plex to tackle. As we recalled the problem is connected to the fact that theData_Received procedure runs in a thread different from the thread thatmanages the graphic component. As we anticipated the solution is to ’del-egate’ the actual graphic update to a procedure running within the graphicthread passing the text to update to it. Note that this is a delicate operationsince involves a cross-threading parameter passage and is performed by usingthe keyword Invoke.

Al already pointed out in section Using delegates to deal with cross-thread, the process is performed in two steps. Firstly we have to declare thatwe are going to use a procedure to ’delegate’ an operation to another thread.To do this we put a global ’delegate’ declaration

delegate void AppendTextCallback(TextBox ctl,

string text);

This statement tell the system we will make use of a procedure namedAppendTextCallback with the shown parameters and that will run in an-other thread (for us the thread that manages the graphic interface).

Then when we want to run the procedure we ’invoke’ it from the properthread:

private void processThisLine(String line)

{

//for now do nothing but show the answer

AppendATextCrossThread(textBoxDataReceived,

">> " + line + "\n");

}

..........

private void AppendATextCrossThread(TextBox ctl, string text)

{

80

Page 82: Introduzione Alla Programmazione Dei SAD

// InvokeRequired compares the thread ID of the

// calling thread to the thread ID of the creating thread.

// If these threads are different, it returns true.

if (ctl.InvokeRequired)

{

//create the callback object and invoke it

AppendTextCallback d =

new AppendTextCallback(AppendATextCrossThread);

this.Invoke(d, new object[] { ctl, text });

}

else

{

ctl.Text = ctl.Text + "\r\n" + text;

}

}

The procedure looks strange but in fact it is quire simple: the parame-ter TextBox ctl is the text box we want to update. When the procedureAppendATextCrossThread is called from the procedure processThisLine

it runs in the thread created by the serial line manager so that the testctl.InvokeRequired returns true. In this case a delegate of typeAppendTextCallback is created that wraps a fresh copy of the procedureAppendATextCrossThread (i.e. of the same procedure that is running!) thena request to the system is made to invoke such procedure within the graphicthread (i.e. the thread identified by the keyword this) The Invoke syntaxlooks strange, but is not so complex:

this.Invoke(d, new object[] { ctl, text });

an invoke is performed for the delegate d passing it a parameter of type objectarray that must contain the (ordered) parameters which are the parametersexpected by the delegate procedure.

The former procedure (the one running in the serial line thread) thenexists, but the delegate (the copy running in the GUI thread) still exists andruns. When the delegate procedure starts, it is in the graphic thread so thatctl.InvokeRequired returns false and the text is updated.

81

Page 83: Introduzione Alla Programmazione Dei SAD

7.4 Revision questions

• How can you use the serial line, what is the difference between usingthe toolbox and adding manually the object?

• What is the serial line handshake and how this is connected to thehardware wiring?

• What are the serial line specific transmission errors?

• How the data arrive from the serial line and how can you collect anduse them?

• What is the cross-thread problem when using the serial line and howcan you solve it?

82

Page 84: Introduzione Alla Programmazione Dei SAD

Chapter 8

Using the National InstrumentIEEE488

8.1 Simple data polling

The complete source code of this example is available in theNI488.Csharp package

BEWARE: in order to compile this program you must have the NationalInstrument GPIB488.2 drivers installed on your computer. If you want torun this program you also need an IEEE488 National Instrument Board.

The use of the National Instrument IEEE488 interface is made throughthe driver provided by NI. In order to use the driver two operations have tobe done.

Firstly we have to add the Dynamic Link Library (dll) which containsthe driver to the solution resources. To do this go to Project/Add Reference(fig. 8.1, select the Net tab and add the two references namedNationalInstruments.NI4882 and NationalInstruments.Common. If youare using a pre-assembled code, you can still encounter a missing referenceerror if the DLL revision present in your computer is not the same which isexpected by the program. If you are using a newer driver release there is agood chance you can still compile the program, simply remove the referencespresent in you code and add them again as explained above.

Secondly we have to import the driver definitions by means of the usualusing directive.

using NationalInstruments.NI4882;

83

Page 85: Introduzione Alla Programmazione Dei SAD

Figure 8.1: The form to be used for adding the National Instrument reference

At this point we can interact with the instruments by using the object oftype Device

NationalInstruments.NI4882.Device gpibDmm;

The device needs to be opened by telling the system which NI488 boardit has to use and which is the instrument address.

private int BOARD = 0;

private byte ADDRESS = 9;

private byte SEC_ADDRESS = 0;

gpibDmm = new NationalInstruments.NI4882.Device(

BOARD,

ADDRESS,

SEC_ADDRESS);

Some remarks:

• the board number depends on the computer configuration, refer toBoard installation to find how to determine such number

• remember that the GPIB is an asynchronous bus: you cannot knowin advance how long a read (or write) operation can require. If it lastforever (usually due to a wrong instrument setting), you may experience

84

Page 86: Introduzione Alla Programmazione Dei SAD

a system hang. To avoid this problem setup an operation timeout(gpibDmm.IOTimeout = TimeoutValue.T10s;)

• The national instrument APIs employ an internal buffer for readingdata which is usually limited to one kilobyte. If you expect to readlarger data block you either need to increase the buffer size(gpibDmm.DefaultBufferSize = 32000;)or setup a read cycle until all the data are extracted from the bus. Youmay check the GpibStatusFlags.IOComplete flag to check if the IOoperation is complete.

Writing commands to the instrument and reading responses are nor easyoperations since they are accomplished through the device object:

gpibDmm.Write("*IDN?\n");

response = gpibDmm.ReadString();

You are strongly advised to surround all instrument related code in atry-catch environment to be able to debug your application

8.2 Waiting for acquisition end without using

the SRQ events

The complete source code of this example is available in theNI488.SRQPolling.CSharp package

BEWARE: in order to understand this section you must be acquainted withthe IEEE488.2 SRQ and status reporting operations.

When you have to perform data acquisition operations that last for awhile and you use a blocking read data method (e.g. a .ReadString) yoursystem may hang until the operation completes. This is the reason you arestrongly advised to set the timeout to a reasonable value, but anyway deadtimes of several seconds should be avoided.

This can be obtained starting the read operation only when the instru-ment is ready to send the results (i.e. the actual measurement operation isfinished). You can use the SRQ bus facility to be notified of this (see thefollowing section), but a similar result can be obtained avoiding the directSRQ use (and the problems connected to the interrupt handler management)

85

Page 87: Introduzione Alla Programmazione Dei SAD

by taking advantage of the IEEE488.2 specifications which define a commoncommand i.e. *OPC specifically designed for this purpose.

Using this technique requires six steps

1. Setup the DMM to perform the long operation without starting it; thisdoes not contain novelties with respect to the previous section and willnot described

2. Enable the operation complete event

3. Cleanup previous events

4. Start the long operation start and issue an OPC request

5. Poll the Status byte until operation completes

6. Read the data

Enabling the generation of the operation complete event requires to setthe Standard Event Status Register with the common command *ESE andService Request Enable register with the common command *SRE. The Stan-dard Event Status Register defines which events will be used to feed theService Request; for the present example you need to enable the OperationComplete event which is the less significant bit (i.e. the bit zero) thus thecommand is:

gpibDmm.Write("*ESE 1\n");

Then you have to define which events will be used to trigger the SRQ request.The bit coming from the standard event register is bit number five thus therequired command is:

gpibDmm.Write("*SRE 32\n");

After this setup the instrument is instructed to generate a SRQ when thelast operation completes, but you must be sure nothing is in the queue andthis can be obtained by manually reading the operation complete bit withthe common command *OPC?

gpibDmm.Write("*OPC?\n");

String strTemp = gpibDmm.ReadString();

86

Page 88: Introduzione Alla Programmazione Dei SAD

At this point you can start the measurement, usually by means of anINIT command, and issue an OPC (without the question mark) command;

gpibDmm.Write("INIT\n");

gpibDmm.Write("*OPC\n");

Now the instrument performs the measurement and when the operationcompletes (i.e. when the *OPC completes its job) it sets the operation com-plete bit in the Standard Event is set. You can periodically test the bit byreading the status byte, usually within the _Tick event of a timer. The SQRis signalled by bit number six (i.e. 0x40). National Instrument defines anenumeration type (SerialPollFlags) to make the code self-explaining, butyou can use integer variables if you prefer

..... timer code

SerialPollFlags statusValue = gpibDmm.SerialPoll();

// Test for bit 6 (the SRQ bit)

if ((statusValue & SerialPollFlags.RequestingService) ==

SerialPollFlags.RequestingService)

.............

Do not forget to reset the instrument and disable the SRQ request atprogram end and to surround the code lines with a try-catch environment.

8.3 Using the SRQ in event mode

The complete source code of this example is available in theNI488.SRQEvent.CSharp package

BEWARE: in order to understand this section you must be acquainted withthe IEEE488.2 SRQ and status reporting operations. You also have tounderstand the concept of threads and cross-thread problems which arediscussed in the serial line chapter (see discussion in the serial line use

chapter)

The solution described in the previous section provides a better behaviorwith respect to the simple wait-for-data approach, however it still wasteprocessing time by polling the status byte. Since the SRQ is an event, the

87

Page 89: Introduzione Alla Programmazione Dei SAD

best way to deal with it is to register an event handler and wait for the eventto be fired.

To do so you need to have to define a procedure that handles the event,to register the procedure within the device driver and to interact with datain the driver thread by means of a Delegate procedure.

The first step is to declare the template of the delegate procedure youwill use to access the driver thread so that the C# compiler knows how todeal with it:

private delegate void NotifyUpdateStatusDelegate(

string readText,

string status,

string count);

private NotifyUpdateStatusDelegate notifyUpdateStatusHandler;

Then you have to write the procedure that will deal with the event ac-cording to the template:

private void gpibDmm_Notify(object sender, NotifyData e){}

The parameter NotifyData is the way to interact with the driver andcontains details about the event (in our case the SRQ) that triggered theprocedure. Note that the procedure name is arbitrary, however here theconvention underscore plus name of the event (i.e. _Notify has been used)

Eventually you have to register the procedure to manage the SQR eventby using the .Notify method:

gpibDmm.Notify(

GpibStatusFlags.DeviceServiceRequest,

new NotifyCallback(gpibDmm_Notify), "Example of callback");

Now you are ready to start your long operation as described in the pre-vious section

..... setup code

gpibDmm.Write("*ESE 1\n");

gpibDmm.Write("*SRE 32\n");

gpibDmm.Write("*OPC?\n");

String strTemp = gpibDmm.ReadString();

88

Page 90: Introduzione Alla Programmazione Dei SAD

gpibDmm.Write("INIT\n");

gpibDmm.Write("*OPC\n");

When the measurement procedure ends and an SRQ event is fired thegpibDmm_Notify procedure is called. Inside the procedure you may read theresults and re-enable the SQR generation (that autodisables by default) fornext measurement.

private void gpibDmm_Notify(object sender, NotifyData e)

{

try

{

gpibDmm.Write("FETCH?\n"); // Ask data

String readings = gpibDmm.ReadString();

gpibDmm.SerialPoll();

e.SetReenableMask(GpibStatusFlags.DeviceServiceRequest);

AppendATextCrossThread(textBoxMessage,readings);

}

catch (Exception exp)

{

MessageBox.Show(exp.Message);

}

}

In the example above, after reading the data, a serial poll is performedto clear the SRQ bit then the signalling mechanism is reenabled. Be warnedthat the code in the procedure is executed within the thread that manages theIEEE488, which is different from the thread that manages the GUI so thatyou cannot call methods that act on graphics components without raisingand illegal cross thread exception and you have to use a delegate, in thisexample the AppendATextCrossThread, which is described in discussion inthe serial line use chapter.

8.4 Revision questions

This section is specific to National Instrument boards thus no specific ques-tions are reported here that are connected to the code. However you must

89

Page 91: Introduzione Alla Programmazione Dei SAD

be able to answer generic questions related to the IEEE488 in order to suc-cessfully arrange a data acquisition program:

• What is the IEEE488 device address?

• What are the different roles an IEEE488 device can play?

• What is the difference between a system controller and a master systemcontroller?

• How many devices can you connect on a IEEE488 bus?

• What is the maximum extension of an IEEE488 bus?

• How the IEEE488 connector is made?

• What is the extended addressing?

• Why the IEEE488 employs 3 handshake lines instead of 2?

• Why the transfer timeout is so important when dealing with the IEEE488?

• Why some lines use the open collector circuit and what is the problemconnected with this solution?

• What is the meaning of MLA, MTA?

• What are the common commands?

• What is the meaning of commands WAI and OPC?

• What are the data terminators and how the IEEE488.2 defines them?

• What are the device capabilities and how are they used by the IEEE488.2?

• What is the maximum expected IEEE488 transfer speed?

• What is the SRQ? How can be used? When is it useful?

• What is the serial poll and what are its advantages?

• What is the parallel poll and how can be used?

• What are the separators defined by the IEEE488.2?

90

Page 92: Introduzione Alla Programmazione Dei SAD

• What is the SCPI?

• How can you program a data transfer between to devices without in-volving the system controller?

• What is the HS488 protocol and what are its advantages?

91

Page 93: Introduzione Alla Programmazione Dei SAD

Chapter 9

Using the Hewlett PackardIEEE488

9.1 Simple data polling

The complete source code of this example is available in theHP-IEEE488.BasicRead.CSharp package

BEWARE: in order to compile this program you must have the Agilent IOLibrary Suite version 15 or above installed on your computer. If you want

to run this program you also need an IEEE488 HP Board.

The use of the Hewlett Packard IEEE488 interface is made through thedriver provided by Agilent (formerly Hewlett Packard). The driver up toversion 15 does not integrate with the Microsoft help system so than youcannot get context help while developing the code. The help is providedin the compiled help file VISACOM.chm, which is available through the menuStart/Agilent IO Library Suite/Help/VISA COM Help

In order to use the driver two operations have to be done.Firstly you have to add the Dynamic Link Library (dll) which contains

the driver to the solution resources. To do this go to Project/Add Reference(fig. 9.1, select the Com tab and add the references namedVISA COM 3.0 Type Library. Should you have to use interfaces from othermanufacturers, consult the manufacturer documentation to see which refer-ence have to be added. If you are using a pre-assembled code, you can stillencounter a missing reference error if the DLL revision present in your com-puter is not the same expected by the program. If you are using a newer

92

Page 94: Introduzione Alla Programmazione Dei SAD

software release there is a good chance you can still compile the program,simply remove the reference present in you code and add them again asexplained above.

Figure 9.1: The form to be used for adding the Agilent reference

Secondly you have to import the driver definitions by means of the usualusing directive.

using Ivi.Visa.Interop;

At this point we can interact with the instruments by using different typesof objects. The simplest way is to use an Ivi.Visa.Interop.FormattedIO488

object which implements the IFormattedIO interface. Such object containsmethods to read and write strings, numbers and binary data. The objectalso embeds a buffer where data can be written until a flush command issent.

private Ivi.Visa.Interop.FormattedIO488 ioDmm;

The device needs to be initialized and this is obtained by using anotherobject referred to as ResourceManager. Each instrument is identified by itsaddress and by the identification string of the IEEE488 board the instrumentis connected to. Agilent drivers employ a double colon separated string whichcan contain multiple fields. The simplest form is BOARB_NAME::ADDRRESS

thus the string GPIB0::22 refers to an instrument with IEEE488 address 22

93

Page 95: Introduzione Alla Programmazione Dei SAD

which is connected to the board identified by the nickname GPIB0. The nameGPIB0 is the default name for the first Agilent IEEE488 board; you can find(and possibly change) the name actually used on your computer by runningthe IO Control program provided by the Agilent library suite in the utilitysection. Please note that this program must be run at least once when a newinterface is added to the computer.

The code to connect the instrument to the FormattedIO488 object canbe summarized as follows:

//create the formatted io object

ioDmm = new FormattedIO488Class();

//define the address as BOARD::id

String instrumentAddress = "GPIB0::12";

//select a reasonable timeout

int timeOutValue_ms = 2000;

//initialize the IO

try

{

ResourceManager grm = new ResourceManager();

ioDmm.IO = (IMessage)grm.Open(instrumentAddress,

AccessMode.NO_LOCK, timeOutValue_ms, "");

}

catch (SystemException ex)

{

MessageBox.Show("Open failed on " +

instrumentAddress + " " + ex.Source

+ " " + ex.Message, "HP488 BasicRead",

MessageBoxButtons.OK,

MessageBoxIcon.Error);

ioDmm.IO = null;

return;

}

Some remarks:

• remember that the IEEE488 is an asynchronous bus: you cannot knowin advance how long a read (or write) operation can last. If it lastsforever (usually due to a wrong instrument setting), you may experience

94

Page 96: Introduzione Alla Programmazione Dei SAD

a system hang. To avoid this problem select a reasonable timeout valuewhen you open the instrument with the resource manager.

At this point you can write and read data from the instrument. The simplestway is to use the WriteString and ReadString methods. Remember that allwriting operations go to the driver buffer and are flushed when a FlushWrite

command is issued (or when the write method is called with the flushAndEndparameter set at true; in this case the method is blocking i.e. it does notreturn until the string is flushed).

ioDmm.WriteString("*IDN?", true);

//the second parameter true force the buffer flush

String response = ioDmm.ReadString();

//read data waiting for a suitable end condition

Remember that the received data is stored inside the driver buffer. If youexpect to have large data block increase the buffer size above the maximumexpected size to simplify the data management.

ioDmm.SetBufferSize(BufferMask.IO_IN_BUF, 30000);

When you no longer need the object you have to close it:

ioDmm.IO.Close();

9.2 Waiting for acquisition end without using

the SRQ events

The complete source code of this example is available in theHP-IEEE488.SRQPolling.CSharp package

BEWARE: in order to understand this section you must be acquainted withthe IEEE488.2 SRQ and status reporting operations.

When you have to perform data acquisition operations that last for awhile and you use a blocking read data method (e.g. a .ReadString) yoursystem may hang until the operation completes. This is the reason you arestrongly advised to set the timeout to a reasonable value, but anyway deadtimes of several seconds should be avoided.

95

Page 97: Introduzione Alla Programmazione Dei SAD

This can be obtained starting the read operation only when the instru-ment is ready to send the results (i.e. the actual measurement operation isfinished). You can use the SRQ bus facility to be notified of this (see thefollowing section), but a similar result can be obtained avoiding the SRQuse (and the problems connected to the interrupt handler management) bytaking advantage of the IEEE488.2 specifications which define a commoncommand i.e. *OPC specifically designed for this purpose.

Using this technique requires six steps

1. Setup the DMM to perform the long operation without starting it; thisdoes not contain novelties and will not described

2. Enable the generation of the operation complete event

3. Cleanup previous events

4. Start the long operation start and issue an OPC request

5. Poll the Status byte until operation completes

6. Read the data

Enabling the generation of the operation complete event requires to setthe Standard Event Status Register with the common command *ESE andService Request Enable register with the common command *SRE. The Stan-dard Event Status Register defines which events will be used to feed theService Request; for the present example you need to enable the OperationComplete event which is the less significant bit (i.e. the bit zero) thus

ioDmm.WriteString("*ESE 1", true);

// Enable ’operation complete bit

//in the standard events

Then you have to define which events will be used to trigger the SRQ request.The bit coming from the standard event is bit number five thus:

ioDmm.WriteString("*SRE 32", true);

After this setup the instrument is instructed to generate a SRQ when thelast operation completes, but you must be sure nothing is in the queue andthis can be obtained by manually reading the operation complete bit withthe common command *OPC?

96

Page 98: Introduzione Alla Programmazione Dei SAD

ioDmm.WriteString("*OPC?", true);

String strTemp = ioDmm.ReadString();

At this point you can start the measurement, usually by means of anINIT command, and setup the OPC command

ioDmm.WriteString("INIT", true);

ioDmm.WriteString("*OPC", true);

Now the instrument performs the measurement and when the operationcompletes it sets the operation complete bit in the Standard Event is set.You can periodically test the bit by reading the status byte, usually withinthe _Tick event of a timer. The SQR is signalled by bit number six (i.e.0x40)

int statusValue = ioDmm.IO.ReadSTB();

if ((statusValue & 0x40) == 0x40)

{ //read here the response

Do not forget to reset the instrument and disable the SRQ request atprogram end.

9.3 Using the SRQ in event mode

The complete source code of this example is available in theHP-IEEE488.SRQEvent.CSharp package

BEWARE: in order to understand this section you must be acquainted withthe IEEE488.2 SRQ and status reporting operations. You also have tounderstand the concept of threads and cross-thread problems which arediscussed in the serial line chapter (see discussion in the serial line use

chapter)

The solution described in the previous section provides a better behaviorwith respect to the simple wait-for-data approach, however it still wasteprocessing time by polling the status byte. Since the SRQ is an event, thebest way to deal with it is to register an event handler and wait for the eventto be fired.

To do so you need to have your class implementing the IEventHandler

interface and this can be obtained by adding the interface name to the classdeclaration which is usual of Form type

97

Page 99: Introduzione Alla Programmazione Dei SAD

public partial class FormSRQEvent : Form, IEventHandler

Declaring that the class implements the IEventHandler interface meansthat the class has to provide an implementation of the method HandleEvent,which is the method that is called when an event is fired:

public void HandleEvent(IEventManager vi,

IEvent theEvent,

int userHandle)

Then you need to register the class to receive the events generated by theIEEE488 driver.

The first step is to extract the event manager interface from the instru-ment:

private IEventManager ioEventManager;

ioEventManager = (IEventManager)ioDmm.IO;

Note that the variable used to represent the event manager must be globalto be reused later.

The second step is to associate the class to the event by using the .InstallHandlermethod and to enable the events by using the .EnableEvent method

ioEventManager.InstallHandler(EventType.EVENT_SERVICE_REQ,

this,

0, 0);

ioEventManager.EnableEvent(EventType.EVENT_SERVICE_REQ,

EventMechanism.EVENT_HNDLR, 0);

The first line installs this instance of the class (i.e. this) as handler forthe service request event (EventType.EVENT_SERVICE_REQ); the second lineenables the event and directs them to the handler.

At this point when a service request is raised the code contained in thehandler event is executed.

public void HandleEvent(IEventManager vi,

IEvent theEvent,

int userHandle)

{

98

Page 100: Introduzione Alla Programmazione Dei SAD

ioDmm.WriteString("FETCH?", true);

String readings = ioDmm.ReadString();

AppendATextCrossThread(this.textBoxMessage,

" Received:" + readings + "\r\n");

}

Be warned that the code in this procedure is executed within the threadthat manages the IEEE488, which is different from the thread that managesthe GUI so that you cannot call methods that act on graphics componentswithout raising and illegal cross thread exception and you have to use adelegate. In the example the delegate is AppendATextCrossThread, which isdescribed in the serial line chapter (see the discussion in the serial line use).

To use the SRQ you have to setup the device to generate it, you have tomanage the OPC ans ESR registers and you have to take care of the differentsituations that may arise in case of errors otherwise you might easily getspurious signals.

Specifically:

• BEFORE enabling the handler it is advisable to be sure any olderpending operation on the device is canceled:

ioDmm.IO.Clear(); //send a device clear to be sure the DMM

//does not have

//pending operations

//since we are going to use the SRQ be sure

//no pending request is present

//the answer should be 0 if 32 we have pending data,

//if 64 or 96 we have a pending SRQ

int xx = ioDmm.IO.ReadSTB();

//now read the standard register, the answer should be 0

//if it is 1 there were a pending operation complete

ioDmm.WriteString("*ESR?", true); //this to clear the

//standard event register

String esr = ioDmm.ReadString();

//if we read the status now the answer MUST be zero

int yy = ioDmm.IO.ReadSTB();

• The device must be set-up to generate the SRQ

99

Page 101: Introduzione Alla Programmazione Dei SAD

ioDmm.WriteString("*CLS", true);

//not really required since we cleared it at open time

ioDmm.WriteString("*ESR?", true); //this to clear the

//standard event register

String esr = ioDmm.ReadString();

ioDmm.WriteString("*ESE 1", true); //activate the OPC bit

ioDmm.WriteString("*SRE 32", true); //activate the SRQ enable

ioDmm.WriteString("*OPC?", true); //clear any pending operation

String strTemp = ioDmm.ReadString();

...... CONFIGURE the DEVICE

ioDmm.WriteString("INIT", true); //start a long operation

ioDmm.WriteString("*OPC", true); //put an OPC request in queue

• Each time you want to restart the the SRQ generation you must re-enable it and you must be sure the OPC bit has been cleared, otherwisethe SRQ wont be generated

ioEventManager.EnableEvent(EventType.EVENT_SERVICE_REQ,

EventMechanism.EVENT_HNDLR, 0);

ioDmm.WriteString("*ESR?", true); //this to clear the

//standard event register

//otherwise the event will

//not be generated again

String strTemp = ioDmm.ReadString();

9.4 Revision questions

This section is specific to Hewlett Packard boards thus no specific questionsare reported here that are connected to the code. However you must be ableto answer generic questions related to the IEEE488 in order to successfullyarrange a data acquisition program. See the questions of previous section.

100

Page 102: Introduzione Alla Programmazione Dei SAD

Chapter 10

Using the National InstrumentDAQ Boards

The complete source code of this example is available in theNIDAQ.Csharp package

BEWARE: in order to compile this program you must have the NationalInstrument NIDAQ-MX drivers installed on your computer. If you want to

run this program you also need a NI-DAQ board.BEWARE: not all NI-DAQ boards support all the feature described in this

section

10.1 Forewords

National Instrument Data Acquisition Boards are complex devices whosecorrect use requires knowledge (though rather basic) of analogue electronics,digital electronic, and signa theory. Since not all users have such a knowledge,NI is going toward solutions that hidethe details of the board operationsproviding reasonable defaultsfor many board working paraments. While thisis correct in most situations, sometime you need to override the defaultsto achieve the best results. Unfortunately, the NI documentation is notdesigned to help you in these operations, however the most important workingparaments areexposed by the drivers so that it is only a matter of deeplyexploring the documentation to find out what you are looking for.

101

Page 103: Introduzione Alla Programmazione Dei SAD

10.2 Generalities

Before using National Instruments DAQ board two preliminary operationsare required that are similar to the National Instrument GPIB preliminaryoperations: Firstly we have to add the Dynamic Link Library (dll) whichcontains the driver to the solution resources. To do this go to Project/AddReference (fig. 8.1, select the Net tab and add the two reference namedNationalInstruments.DAQmx and NationalInstruments.Common. If youare using a pre-assembled code, you can still encounter a missing referenceerror if the DLL revision present in your computer is not the same which isexpected by the program. If you are using a newer driver release there is agood chance you can still compile the program, simply remove the referencespresent in you code and add them again as explained above.

Secondly we have to import the driver definitions by means of the usualusing directive.

using NationalInstruments.DAQmx;

National Instrument Data Acquisition Boards are complex devices thatcan comprise analog to digital converters, digital to analog converters, digitalinput/output channels, timers, counters, .....

Using a DAQ board is therefore a complex operations which requires aseries of steps before the actual use can take place. The basic componentwhich is used is a Task which is the container for channels, timings andformatters. A task support only one type of channels (i.e. one type ofoperations such as sampling values or generating values) at a time, so if youwant to perform data input and data output at the same time you need twodifferent tasks.

Exploring the the details of all the operations you can perform with theDAQ boards is beyond the scope of this primer, so that here only the basicsof data sampling and data generation will be explored.

We will deal with tasks in the following however some general remarksare valid regardless of the types of operation you wish to perform with yourtask

The most important thing to keep in mind is that the task settings (e.g.number of channels, sampling rate,..) are silently passed to the task withoutperforming a check that the hardware is capable of supporting them. Ofcourse such a check must be performed before using the board, but this isperformed only when explicitly calling the task Control method

102

Page 104: Introduzione Alla Programmazione Dei SAD

Such a method takes a parameter which also allows you to alter the taskbehavior. The possible values are:

TaskAction.Abort Aborts execution of the task. Aborting a task immediately terminatesthe currently active operation, such as a read or a write. Aborting atask puts the task into an unstable but recoverable state. To recoverthe task, use Start to restart the task or use Stop to reset the taskwithout starting it.

TaskAction.Commit Programs the hardware with all parameters of the task thus speedingup the execution of the subsequent operations.

TaskAction.Reserve Marks the hardware resources that are needed for the task as in use.No other tasks can reserve these same resources.

TaskAction.Unreserve Releases all previously reserved resources.

TaskAction.Start Move the task to the running state, which begins device input or out-put.

TaskAction.Stop Move the task from the running state to the committed state, whichends device input or output.

TaskAction.Verify Verifies that all task parameters are valid for the hardware.

10.3 Data reading

As said, the access to the hardware is obtained through the Task object. Atask is a collection of virtual channels whose properties are defined by addingchannels to the task itself.

The input channels are referred to as AIChannels and you have to add tothe analog input channel collection the channels you want to use by creatingthem

readTask.AIChannels.CreateVoltageChannel......

The driver allows you to create an enormous amount of different channeltypes (thermocouple, resistance,...) suitable for different operations, howeverkeep in mind that the board has only input voltage channels so that the otherchannel types are software-created. Moreover, some of the virtual channel

103

Page 105: Introduzione Alla Programmazione Dei SAD

types (e.g. resistance channels) may require more than one physical channel.For this reason I suggest you to avoid the use of complex setting whose detailsare not clear: with little effort you can create any kind of software processingstill having all the details clear and controlled.

The CreateVoltageChannel is therefore the basic way of dealing withinput channels and we will discuss this section only.

The syntax is

.... CreateVoltageChannel(

string physicalChannelName,

string nameToAssignChannel,

AITerminalConfiguration terminalConfiguration,

double minimumValue,

double maximumValue,

AIVoltageUnits units

);

where the parameters define the way the channel(s) is(are) created

physicalChannelName is the string with the names of one or more physical channels to use tocreate one or more local virtual channels in the form "Devxxx/ai0:1"

where Devxxx is the name assigned (by the configuration program e.g.by Measurement and Automation Explorer) to the DAQ board and ai0

... aiXX represent the physical channels. A set of consecutive channelscan be specified by using the colon character, non consecutive channelsmay be specified by separating them with colons.

nameToAssignChannel One or more names to assign to the created local virtual channels. Touse the physical channel name as the local virtual channel name, setthis value to Empty.

terminalConfiguration The input terminal configuration. This is a critical choice since theacquisition performance strongly depends on this choice. The types ofterminal configurations are listed in AITerminalConfiguration

minimumValue The minimum value expected from the measurement, in units. Payattention to this value since it affects the way the board is configured.More details on this subject can be found in Manual acquisition finetuning

104

Page 106: Introduzione Alla Programmazione Dei SAD

maximumValue The maximum value expected from the measurement, in units. Payattention to this value since it affects the way the board is configured.

units The units to use to return the measurement.

Once the channels have been created you can either acquire a single valueor use the board pacing to sample your inputs.

The pacing is set through the timing method:

......ConfigureSampleClock(

string signalSource,

double rate,

SampleClockActiveEdge activeEdge,

SampleQuantityMode sampleMode,

int samplesPeChannel

);

where

signalSource The source terminal for the clock. To use the board internal clock , setthis value to the empty string.

rate The sampling rate in samples per second. If you use an external sourcefor the sample clock, set this input to the maximum expected rate ofthat clock. BEWARE this is not the actual ADC sampling frequency,see Manual acquisition fine tuning for more details

activeEdge The edges of sample clock pulses on which to acquire or generate sam-ples. If you use the internal clock you can safely useSampleClockActiveEdge.Rising

sampleMode The duration of the task. A task is either finite and stops once thespecified number of samples have been acquired or generated, or it iscontinuous and continues to acquire or generate samples until the taskis explicitly stopped.

samplesPerChannel The number of samples to acquire or generate if sampleMode is setto FiniteSamples. If sample mode is set to ContinuousSamples, NI-DAQmx uses this value to determine the buffer size.

105

Page 107: Introduzione Alla Programmazione Dei SAD

Be warned that both in channels and timing you can set wrong or notpossible values without receiving error messages. Moreover, if the driverfound a way to satisfy your request, it coerce (i.e. force) your parametersto the ones actually available in the board. All the parameters are validatedwhen the task is checked for correctness with the .Control method. A taskcannot be used if it is not verified.

readTask.Control(TaskAction.Verify);

At this point the task is ready to be used and you can get the data.Although you can use also a raw data reading, the best solution to obtainthe data is to use a formatter reader which is able to return a matrix whichis automatically reshaped according to the number of channels you preparedthe task with.

The raw data read produces a vector of bytes whose length is equals tothe number of samples times the number of channels time the size of eachsample (typically two since most boards gives results with 16 bit resolution).

byte[] rawData = readTask.Stream.ReadRaw(nSamplesToReadPerChannel);

There are two types of analog reader: scaled and unscaled. The formeremploys the acquisition board calibration parameters to return a vector dou-bles which are voltages (since we are using a voltage channel), the latterreturn the binary data produced by the analog to digital converter so that itlead to a more compact data representation.

//scaled reader

reader = new AnalogMultiChannelReader(readTask.Stream);

//unscaled reader

readerU = new AnalogUnscaledReader(readTask.Stream);

//read scaled

double[,] data = reader.ReadMultiSample

(nSamplesToReadPerChannel*nChannels);

//read unscaled

Int16[,] dataU = readerU.ReadInt16

(nSamplesToReadPerChannel*nChannels);

106

Page 108: Introduzione Alla Programmazione Dei SAD

.....

}

Please note that the default configuration of the board is that when youattempt to read data if they are not available the task is moved to the startstate to collect the data. For this reason you do not see here an explicit callto:

readTask.Control(TaskAction.Start);

Also note that if you try reading the stream twice a new acquisition istriggered each time you read..

Eventually, when you have done with your task remember to clean up thesystem by disposing the task itself. Since tasks are complex objects there is achance the system garbage collector (which should take care of anything leftaround and no longer used...) get confused and leave the object consumingmemory. Moreover, since there is a not negligible possibility of encounteringerrors using the board it is a good programming choice to encapsulate all theboard related code in a try/catch/finnaly structure:

Task readTask=null;

try

{

readTask = new Task();

//create two channels

........

}

catch (DaqException exception)

{

MessageBox.Show(exception.Message);

}

finally

{

readTask.Dispose();

}

107

Page 109: Introduzione Alla Programmazione Dei SAD

10.4 Generating voltages

Voltage generation using the digital to analog converters is obtained byadding output channels to a task:

Task writeTask=null;

AnalogMultiChannelWriter writer=null;

//create a write task

try

{

writeTask = new Task();

writeTask.AOChannels.CreateVoltageChannel(

"Dev1/ao0:1", "", 0, 5,

AOVoltageUnits.Volts);

writeTask.Control(TaskAction.Verify);

writer = new AnalogMultiChannelWriter(writeTask.Stream);

double[,] dataOut = new double[2,1];

dataOut[0,0] = 3;

dataOut[1, 0] = 1;

writer.WriteMultiSample(true, dataOut);

}

catch (DaqException exception)

{

MessageBox.Show(exception.Message);

}

finally

{

writeTask.Dispose();

}

The output channels also allow the generation of waveforms (at leastfor some types of boards) by using the continuous mode. The code issimilar the example above, but with few noticeable differences. The firstmain difference is that, since the Task which generates the signal has to rununtil it is explicitly required to stop, the task declaration has to be globaland if must not be disposed at the end of preparation,

108

Page 110: Introduzione Alla Programmazione Dei SAD

AnalogSingleChannelWriter writer = null;

//create a write task

try

{

globalTask = new Task();

globalTask.AOChannels.CreateVoltageChannel(dev+"/ao0",

"", -5, 5,

AOVoltageUnits.Volts);

int nSamplesInBuffer = 2000;

double sampleRatePerChannel = 2000;

globalTask.Timing.ConfigureSampleClock("",

sampleRatePerChannel,

SampleClockActiveEdge.Rising,

SampleQuantityMode.ContinuousSamples,

nSamplesInBuffer);

globalTask.Control(TaskAction.Verify);

writer = new AnalogSingleChannelWriter(

globalTask.Stream);

double[] dataOut = new double[nSamplesInBuffer];

double delta = 2 * Math.PI / nSamplesInBuffer;

for (int i = 0; i < nSamplesInBuffer; i++)

dataOut[i] = 5*Math.Sin(i * delta);

writer.WriteMultiSample(false, dataOut);

globalTask.Start();

}

catch (DaqException exception)

{

MessageBox.Show(exception.Message);

globalTask.Dispose();

}

finally

{

//the MUST remain active;

}

109

Page 111: Introduzione Alla Programmazione Dei SAD

As you can see, the (global) task is prepared by adding an output channelas usual, then the board timing is prepared, selecting the optionSampleQuantityMode.ContinuousSamples.

.............

int nSamplesInBuffer = 2000;

double sampleRatePerChannel = 2000;

globalTask.Timing.ConfigureSampleClock("",

sampleRatePerChannel,

SampleClockActiveEdge.Rising,

SampleQuantityMode.ContinuousSamples,

nSamplesInBuffer);

At this point a writer is created and used to write the samples into theboard buffer. Eventually the generation started and the procedure ends.This way the task scans the buffer with the specified timing and sends thesamples to the analog to digital converter actually generating the signal. Thetask continues generating the waveform until it is explicitly stopped:

....

globalTask.Stop();

....

10.5 Using digital inputs and outputs

Digital data input and output are performed by using respectively DIChannels

and DOChannels. Both type of channels allows you to address either singlelines or groups of lines. Usually lines are referred to as lineXX so that thecode for an input operation typically looks like:

Task digitalTaskIn = null;

DigitalSingleChannelReader digitalReader = null;

//create a write task

try

{

digitalTaskIn = new Task();

digitalTaskIn.DIChannels.CreateChannel(

"Dev1/line0:3", "",

110

Page 112: Introduzione Alla Programmazione Dei SAD

ChannelLineGrouping.OneChannelForAllLines);

digitalTaskIn.Control(TaskAction.Verify);

digitalReader = new

DigitalSingleChannelReader(digitalTaskIn.Stream);

byte b = digitalReader.ReadSingleSamplePortByte();

this.textBoxDigital.AppendText(

"Digital read: "+b.ToString()+"\r\n");

}

catch (DaqException exception)

{

MessageBox.Show(exception.Message);

}

finally

{

digitalTaskIn.Dispose();

}

while for output operations:

Task digitalTaskOut=null;

DigitalSingleChannelWriter digitalWriter = null;

//create a write task

try

{

digitalTaskOut = new Task();

digitalTaskOut.DOChannels.CreateChannel("Dev1/line4:7", "",

ChannelLineGrouping.OneChannelForAllLines);

digitalTaskOut.Control(TaskAction.Verify);

digitalWriter = new

DigitalSingleChannelWriter(digitalTaskOut.Stream);

bool[] boolData = new bool[4];

boolData[1] = false;

digitalWriter.WriteSingleSampleMultiLine(true, boolData);

this.textBoxDigital.AppendText("Written digital data\r\n");

111

Page 113: Introduzione Alla Programmazione Dei SAD

}

catch (DaqException exception)

{

MessageBox.Show(exception.Message);

}

finally

{

digitalTaskOut.Dispose();

}

10.6 Performing continuous acquisitions

The complete source code of this example is available in theNIDAQ-CallBackAcquisition package

BEWARE: in order to compile this program you must have the NationalInstrument NIDAQ-MX drivers installed on your computer. If you want to

run this program you also need a NI-DAQ board.BEWARE: not all NI-DAQ boards support all the feature described in this

section

This example shows how to perform a long asynchronous acquisition,i.e. a long acquisition during which you can deal with the samples youhave already got without stopping the acquisition itself. An asynchronousacquisition consists of two sections:

• a command which starts the acquisition (BeginRead.....) and con-nects the task to a procedure which is activated when the requestednumber of samples has got

• a callback procedure which is fired when the requested number ofsamples has present and that must remove the samples from the bufferand restart the reading.

Note that the procedure of data removal from the buffer and acquisitionrestart is a critical operation since it has to be performed before the nextsample of the stream has to be acquired. It is therefore quite difficult tooperate in this way if the requested sampling frequency is high. Techniques

112

Page 114: Introduzione Alla Programmazione Dei SAD

to obtain a seamless stream of data are available that are beyond the scopeof this tutorial. The code here shown is only a simplified version still suitableto learn how to deal with the callback operations.

Initially you have to declare and prepare the task as already shown, butwith the timing set to ContinuousSamples

globalTask.Timing.ConfigureSampleClock("",

samplingFreq,

SampleClockActiveEdge.Rising,

SampleQuantityMode.ContinuousSamples,

BLOCK_LEN);

Once the task has been verified, you have to set-up the reader (eitherunscaled or scaled) you want to use to extract the samples

analogUnscaledReader = new AnalogUnscaledReader(globalTask.Stream);

then, before starting the data acquisition you have to create the procedurewhich will handle the sample blocks as they arrive. Such a procedure mustbe declared as delegate (see delegate) since it executes in the main thread bymeans of the keyword AsyncCallback

AsyncCallback analogCallback;

........

analogCallback = new AsyncCallback(AnalogInCallback);

........

private void AnalogInCallback(IAsyncResult ar)

{

........

as you see from this code fragment we have a global declaration followed bythe object creation which points to the actual procedure code (AnalogInCallback).This procedure has only a single parameter of type IAsyncResult.

The code contained inside the delegate procedure has to copy and removethe data from the acquisition buffer and store them somewhere they can be

113

Page 115: Introduzione Alla Programmazione Dei SAD

processed and has to restart the acquisition in order to receive other notifi-cations. Please observe that it is important this procedure executes quicklymoving lengthy processes in another place (i.e. in a process which executesin another thread). In the code fragment that follows the procedure simplycopy the data by using an Array operation in a way suitable to preserve themultichannel data integrity:

private void AnalogInCallback(IAsyncResult ar)

{

try

{

{

//Read the available data from the channels

Int16[,] data = analogUnscaledReader.EndReadInt16(ar);

for (int i = 0; i < N_CHANNELS; i++)

{

int sourceStart = i * BLOCK_LEN;

int destPos = actualLenght + i * MAX_LEN;

Array.Copy(data,

sourceStart,

queuedRawData,

destPos,

BLOCK_LEN);

}

actualLenght += BLOCK_LEN;

this.textBoxStatus.AppendText("Got "+

actualLenght.ToString()+"\r\n");

if (actualLenght < MAX_LEN & running)

{

analogUnscaledReader.BeginReadInt16(

BLOCK_LEN,

analogCallback, null);

}else{

114

Page 116: Introduzione Alla Programmazione Dei SAD

this.textBoxStatus.AppendText("Done\r\n");

running = false;

}

}

}

catch (DaqException ex)

{

MessageBox.Show(ex.Message);

globalTask.Dispose();

}

}

10.7 Using DAQ boards in trigger mode

The complete source code of this example is available in theNIDAQ-Trigger package

BEWARE: in order to compile this program you must have the NationalInstrument NIDAQ-MX drivers installed on your computer. If you want to

run this program you also need a NI-DAQ board.BEWARE: not all NI-DAQ boards support all the feature described in this

section

This example shows how to perform a triggered acquisition, i.e. an ac-quisition that starts only after a specific (hardware) event occurs.

Some DAQ boards (check the board documentation!) support triggerchannels of digital and/or analogue types. This means that an acquisitiontask can be armed and instructed to start the actual acquisition only after atrigger event. Please note this facility requires a dedicated hardware insidethe DAQ board and therefore is available only in selected devices.

Using the trigger is easy: you simply instruct the task to use the trigger

globalTask = new Task();

... set channels

globalTask.Timing.ConfigureSampleClock("",

sampleRatePerChannel,

SampleClockActiveEdge.Rising,

115

Page 117: Introduzione Alla Programmazione Dei SAD

SampleQuantityMode.FiniteSamples,

nSamplesToReadPerChannel);

select the trigger type (in this example digital, rising, channel zero)

globalTask.Triggers.StartTrigger.Type =

StartTriggerType.DigitalEdge;

globalTask.Triggers.StartTrigger.DigitalEdge.Edge =

DigitalEdgeStartTriggerEdge.Rising;

globalTask.Triggers.StartTrigger.DigitalEdge.Source =

"/Dev1/PFI0";

globalTask.Control(TaskAction.Verify);

when you start the task:

globalTask.Start();

the board waits for the trigger before acquiring the data. This means that aread operation such as:

double[,] trgData =

reader.ReadMultiSample(nSamplesToReadPerChannel * nChannels);

does not return (and freeze the thread) waiting for available data until thetrigger arrives. Of course you can avoid this behavior using an asynchronousreading as described in the Continuous acquisition

10.8 Manual acquisition fine tuning

As said in the previous sections, the NIDAQ driver sometimes coercestheparameters you may set in order to cope with the acquisition system capa-bilities and sometimes set the parameters according to strategies that mightnot be what you need. In these case you may want to override the defaultsby manually setting the hardware values. This requires knowing the hard-ware capabilities of your hardware, but can optimize the system performance.Dealing with all the parameters is beyond the scope of this guide, howevertwo aspects may be important in measurement application and need to be ex-plained: the actual acquisition timing and the input programmable amplifier(PGA) setting.

116

Page 118: Introduzione Alla Programmazione Dei SAD

10.8.1 Manually setting the acquisition timing

As said before, the acquisition pacing is set through the task ConfigureSampleClock

method. This method takes a single rate parameter which is the overall sam-pling sampling frequency. This means that if you configured your board formultichannel sampling the rate is the frequency of the group of samples,not the actual Analog to Digital Converter (ADC) frequency that is higherthan the rate (at least rate times the number of configured channels). Bydefault the driver selects the minimum possible delay between the samplesof the same group plus a fixed delay of 10µs (i.e. uses the ADC at the maxi-mum speed but 10µs). This solution minimize the skew between samples ineach group, but may increase the errors due to the settling time of the inputstages. In this case you may manually set the conversion rate between thesamples by using the task Timing.AIConvertRate method.

Be warned that the Timing.AIConvertRate cannot exceed the maximumvalue (i.e. Timing.AIConvertMaximumRate) and that the interval betweenthe group of samples (set by the rate parameter in the Timing.ConfigureSampleClock)must be compatible with the selected date and the number of samples

int nSamplesToReadPerChannel = 2000;

int nChannels = 2; //we used ai0 and ai1

double sampleRatePerChannel = 2000;

readTask.Timing.ConfigureSampleClock("",

sampleRatePerChannel,

SampleClockActiveEdge.Rising,

SampleQuantityMode.FiniteSamples,

nSamplesToReadPerChannel);

//by configuring the sample clock you define the interval

//between GROUPS of samples composed of the convertion of the

//selected channels

//

//the interval between samples within the same group

//(i.e. the ConvertRate in NI words) is

//set by default at the maximum board capability (sometime

// adding a delay of 10us to allow the multiplexer to stabilize

//if you need to change this default you have to override

//the AIConvertRate value in Hertz

readTask.Timing.AIConvertRate=100000;

117

Page 119: Introduzione Alla Programmazione Dei SAD

10.8.2 Manually setting the input gain

When you create a voltage channel with the CreateVoltageChannel method:

AIChannel chA=readTask.AIChannels.CreateVoltageChannel(dev + "/ai0:1", "0-1",

AITerminalConfiguration.Differential, MAXV, MAXV,

AIVoltageUnits.Volts);

The driver uses MAXV and MAXV to choose the gain of the input stages. Theactual selected gain depends on the used bard and on its configuration andcan be inspected (after the TaskAction.Verify) by reading the properties

chB.RangeHigh;

chB.RangeLow;

The same parameters can be used to set the values, however rememberthe driver can coerce the values to cope with the board constraints (e.g. insome boards you cannot set different ranges on different channels)

10.9 Revision questions

This section is specific to National Instrument boards thus no specific ques-tions are reported here that are connected to the code. However you mustbe able to answer generic questions related to the IEEE488 in order to suc-cessfully arrange a data acquisition program.

• What is the meaning of differential, single ended, referenced singleended input?

• What are the problems you can encounter using the wrong input con-nection?

• What are the problems connected to the use of not galvanically coupledsignals?

• What are the problems connected to the finite input impedance andwhen they become important?

• What is the problem of the common mode signals?

118

Page 120: Introduzione Alla Programmazione Dei SAD

• What is the CMRR and what are the problems connected to its finitevalue?

• What is the problem of settling time and how can it affect the acquisi-tion of multiple signals?

• How the sampling frequency can be selected and what is the problemof signal reconstruction?

• What are the most common interpolators?

119

Page 121: Introduzione Alla Programmazione Dei SAD

Chapter 11

Interface installation and usefullinks

In this chapter you can find some advice on how to install the hardwarewhich is used in this tutorial. If you are using this tutorial within coursesof Politecnico di Torino you do not need to install anything, since the basicsoftware you are going to use has already been installed.

11.1 National Instrument

The drivers required to use the National Instrument boards can be down-loaded at http://www.ni.com/support/. Check the licensing rules to see ifyou are entitled to download and use a specific piece of software.

Once the drivers have been installed you can configure the different boardsand find the addresses and codes to be used in you code by starting theMeasurement \& Automation program. Fig. 11.1 shows an example ofscreen shot where a GPIB board with nickname GPIB0 and address 0 isdisplayed.

11.2 Agilent

The drivers required to use the Agilent boards can be downloaded athttp://www.home.agilent.com/ in the section Test and measurement software.Check the licensing rules to see if you are entitled to download and use a spe-cific piece of software.

120

Page 122: Introduzione Alla Programmazione Dei SAD

Figure 11.1: The Measurement & Automation Screen

Once the drivers have been installed you can configure the different boardsand find the addresses and codes to be used in you code by starting theIO Control program. Fig. 11.2 shows an example of screen shot where aGPIB board with nickname GPIB0 is displayed.

121

Page 123: Introduzione Alla Programmazione Dei SAD

Figure 11.2: The Agilent IO COnfiguration Screen

122

Page 124: Introduzione Alla Programmazione Dei SAD

Chapter 12

GPL Licence

The GNU General Public License (GPL) Version 2, June 1991 Copyright(C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330,

Boston, MA 02111-1307 USAEveryone is permitted to copy and distribute verbatim copies of this license

document, but changing it is not allowed.

12.1 Preamble

The licenses for most software are designed to take away your freedom toshare and change it. By contrast, the GNU General Public License is in-tended to guarantee your freedom to share and change free software–to makesure the software is free for all its users. This General Public License appliesto most of the Free Software Foundation’s software and to any other programwhose authors commit to using it. (Some other Free Software Foundationsoftware is covered by the GNU Library General Public License instead.)You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price.Our General Public Licenses are designed to make sure that you have thefreedom to distribute copies of free software (and charge for this service ifyou wish), that you receive source code or can get it if you want it, that youcan change the software or use pieces of it in new free programs; and thatyou know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone todeny you these rights or to ask you to surrender the rights. These restrictions

123

Page 125: Introduzione Alla Programmazione Dei SAD

translate to certain responsibilities for you if you distribute copies of thesoftware, or if you modify it.

For example, if you distribute copies of such a program, whether free orfor a fee, you must give the recipients all the rights that you have. You mustmake sure that they, too, receive or can get the source code. And you mustshow them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and(2) offer you this license which gives you legal permission to copy, distributeand/or modify the software.

Also, for each author’s protection and ours, we want to make certain thateveryone understands that there is no warranty for this free software. If thesoftware is modified by someone else and passed on, we want its recipients toknow that what they have is not the original, so that any problems introducedby others will not reflect on the original authors’ reputations.

Finally, any free program is threatened constantly by software patents.We wish to avoid the danger that redistributors of a free program will in-dividually obtain patent licenses, in effect making the program proprietary.To prevent this, we have made it clear that any patent must be licensed foreveryone’s free use or not licensed at all.

The precise terms and conditions for copying, distribution and modifica-tion follow.

12.2 TERMS AND CONDITIONS FOR

COPYING, DISTRIBUTION AND

MODIFICATION

0. This License applies to any program or other work which contains anotice placed by the copyright holder saying it may be distributed un-der the terms of this General Public License. The ”Program”, below,refers to any such program or work, and a ”work based on the Pro-gram” means either the Program or any derivative work under copy-right law: that is to say, a work containing the Program or a portionof it, either verbatim or with modifications and/or translated into an-other language. (Hereinafter, translation is included without limitationin the term ”modification”.) Each licensee is addressed as ”you”.

Activities other than copying, distribution and modification are not

124

Page 126: Introduzione Alla Programmazione Dei SAD

covered by this License; they are outside its scope. The act of runningthe Program is not restricted, and the output from the Program iscovered only if its contents constitute a work based on the Program(independent of having been made by running the Program). Whetherthat is true depends on what the Program does.

1. You may copy and distribute verbatim copies of the Program’s sourcecode as you receive it, in any medium, provided that you conspicuouslyand appropriately publish on each copy an appropriate copyright noticeand disclaimer of warranty; keep intact all the notices that refer tothis License and to the absence of any warranty; and give any otherrecipients of the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, andyou may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Program or any portion ofit, thus forming a work based on the Program, and copy and distributesuch modifications or work under the terms of Section 1 above, providedthat you also meet all of these conditions:

a) You must cause the modified files to carry prominent notices stat-ing that you changed the files and the date of any change.

b) You must cause any work that you distribute or publish, that inwhole or in part contains or is derived from the Program or anypart thereof, to be licensed as a whole at no charge to all thirdparties under the terms of this License.

c) If the modified program normally reads commands interactivelywhen run, you must cause it, when started running for such in-teractive use in the most ordinary way, to print or display anannouncement including an appropriate copyright notice and anotice that there is no warranty (or else, saying that you providea warranty) and that users may redistribute the program underthese conditions, and telling the user how to view a copy of thisLicense. (Exception: if the Program itself is interactive but doesnot normally print such an announcement, your work based onthe Program is not required to print an announcement.)

These requirements apply to the modified work as a whole. If iden-tifiable sections of that work are not derived from the Program, and

125

Page 127: Introduzione Alla Programmazione Dei SAD

can be reasonably considered independent and separate works in them-selves, then this License, and its terms, do not apply to those sectionswhen you distribute them as separate works. But when you distributethe same sections as part of a whole which is a work based on theProgram, the distribution of the whole must be on the terms of this Li-cense, whose permissions for other licensees extend to the entire whole,and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest yourrights to work written entirely by you; rather, the intent is to exercisethe right to control the distribution of derivative or collective worksbased on the Program.

In addition, mere aggregation of another work not based on the Pro-gram with the Program (or with a work based on the Program) on avolume of a storage or distribution medium does not bring the otherwork under the scope of this License.

3. You may copy and distribute the Program (or a work based on it, underSection 2) in object code or executable form under the terms of Sections1 and 2 above provided that you also do one of the following:

a) Accompany it with the complete corresponding machine-readablesource code, which must be distributed under the terms of Sec-tions 1 and 2 above on a medium customarily used for softwareinterchange; or,

b) Accompany it with a written offer, valid for at least three years, togive any third party, for a charge no more than your cost of physi-cally performing source distribution, a complete machine-readablecopy of the corresponding source code, to be distributed under theterms of Sections 1 and 2 above on a medium customarily usedfor software interchange; or,

c) Accompany it with the information you received as to the offer todistribute corresponding source code. (This alternative is allowedonly for noncommercial distribution and only if you received theprogram in object code or executable form with such an offer, inaccord with Subsection b above.)

The source code for a work means the preferred form of the work formaking modifications to it. For an executable work, complete source

126

Page 128: Introduzione Alla Programmazione Dei SAD

code means all the source code for all modules it contains, plus anyassociated interface definition files, plus the scripts used to controlcompilation and installation of the executable. However, as a specialexception, the source code distributed need not include anything thatis normally distributed (in either source or binary form) with the majorcomponents (compiler, kernel, and so on) of the operating system onwhich the executable runs, unless that component itself accompaniesthe executable.

If distribution of executable or object code is made by offering accessto copy from a designated place, then offering equivalent access to copythe source code from the same place counts as distribution of the sourcecode, even though third parties are not compelled to copy the sourcealong with the object code.

4. You may not copy, modify, sublicense, or distribute the Program ex-cept as expressly provided under this License. Any attempt otherwiseto copy, modify, sublicense or distribute the Program is void, and willautomatically terminate your rights under this License. However, par-ties who have received copies, or rights, from you under this Licensewill not have their licenses terminated so long as such parties remainin full compliance.

5. You are not required to accept this License, since you have not signed it.However, nothing else grants you permission to modify or distribute theProgram or its derivative works. These actions are prohibited by law ifyou do not accept this License. Therefore, by modifying or distributingthe Program (or any work based on the Program), you indicate youracceptance of this License to do so, and all its terms and conditions forcopying, distributing or modifying the Program or works based on it.

6. Each time you redistribute the Program (or any work based on theProgram), the recipient automatically receives a license from the origi-nal licensor to copy, distribute or modify the Program subject to theseterms and conditions. You may not impose any further restrictionson the recipients’ exercise of the rights granted herein. You are notresponsible for enforcing compliance by third parties to this License.

7. If, as a consequence of a court judgment or allegation of patent infringe-ment or for any other reason (not limited to patent issues), conditions

127

Page 129: Introduzione Alla Programmazione Dei SAD

are imposed on you (whether by court order, agreement or otherwise)that contradict the conditions of this License, they do not excuse youfrom the conditions of this License. If you cannot distribute so asto satisfy simultaneously your obligations under this License and anyother pertinent obligations, then as a consequence you may not dis-tribute the Program at all. For example, if a patent license would notpermit royalty-free redistribution of the Program by all those who re-ceive copies directly or indirectly through you, then the only way youcould satisfy both it and this License would be to refrain entirely fromdistribution of the Program.

If any portion of this section is held invalid or unenforceable under anyparticular circumstance, the balance of the section is intended to applyand the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patentsor other property right claims or to contest validity of any such claims;this section has the sole purpose of protecting the integrity of the freesoftware distribution system, which is implemented by public licensepractices. Many people have made generous contributions to the widerange of software distributed through that system in reliance on consis-tent application of that system; it is up to the author/donor to decideif he or she is willing to distribute software through any other systemand a licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed tobe a consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in certaincountries either by patents or by copyrighted interfaces, the originalcopyright holder who places the Program under this License may add anexplicit geographical distribution limitation excluding those countries,so that distribution is permitted only in or among countries not thusexcluded. In such case, this License incorporates the limitation as ifwritten in the body of this License.

9. The Free Software Foundation may publish revised and/or new versionsof the General Public License from time to time. Such new versionswill be similar in spirit to the present version, but may differ in detailto address new problems or concerns.

128

Page 130: Introduzione Alla Programmazione Dei SAD

Each version is given a distinguishing version number. If the Programspecifies a version number of this License which applies to it and ”anylater version”, you have the option of following the terms and conditionseither of that version or of any later version published by the FreeSoftware Foundation. If the Program does not specify a version numberof this License, you may choose any version ever published by the FreeSoftware Foundation.

10. If you wish to incorporate parts of the Program into other free programswhose distribution conditions are different, write to the author to askfor permission. For software which is copyrighted by the Free SoftwareFoundation, write to the Free Software Foundation; we sometimes makeexceptions for this. Our decision will be guided by the two goals ofpreserving the free status of all derivatives of our free software and ofpromoting the sharing and reuse of software generally.

NO WARRANTY

11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE,THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EX-TENT PERMITTED BY APPLICABLE LAW. EXCEPT WHENOTHERWISE STATED IN WRITING THE COPYRIGHT HOLD-ERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM”AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EX-PRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,THE IMPLIED WARRANTIES OF MERCHANTABILITY ANDFITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ASTO THE QUALITY AND PERFORMANCE OF THE PROGRAMIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,YOU ASSUME THE COST OF ALL NECESSARY SERVICING, RE-PAIR OR CORRECTION.

12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW ORAGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER,OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDIS-TRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLETO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPE-CIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISINGOUT OF THE USE OR INABILITY TO USE THE PROGRAM (IN-CLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA

129

Page 131: Introduzione Alla Programmazione Dei SAD

BEING RENDERED INACCURATE OR LOSSES SUSTAINED BYYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAMTO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCHHOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POS-SIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

12.3 How to Apply These Terms to Your New

Programs

If you develop a new program, and you want it to be of the greatest possibleuse to the public, the best way to achieve this is to make it free softwarewhich everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest toattach them to the start of each source file to most effectively convey theexclusion of warranty; and each file should have at least the ”copyright” lineand a pointer to where the full notice is found.

one line to give the program’s name and a brief idea of what it does.Copyright (C)

This program is free software; you can redistribute it and/or modify itunder the terms of the GNU General Public License as published by the FreeSoftware Foundation; either version 2 of the License, or (at your option) anylater version.

This program is distributed in the hope that it will be useful, but WITH-OUT ANY WARRANTY; without even the implied warranty of MER-CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.

You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free Software Foundation, Inc.,59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Also add information on how to contact you by electronic and paper mail.If the program is interactive, make it output a short notice like this when

it starts in an interactive mode:Gnomovision version 69, Copyright (C) year name of author Gnomovision

comes with ABSOLUTELY NO WARRANTY; for details type ‘show w’.

130

Page 132: Introduzione Alla Programmazione Dei SAD

This is free software, and you are welcome to redistribute it under certainconditions; type ‘show c’ for details.

The hypothetical commands ‘show w’ and ‘show c’ should show the ap-propriate parts of the General Public License. Of course, the commands youuse may be called something other than ‘show w’ and ‘show c’; they couldeven be mouse-clicks or menu items–whatever suits your program.

You should also get your employer (if you work as a programmer) or yourschool, if any, to sign a ”copyright disclaimer” for the program, if necessary.Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright interest in the program‘Gnomovision’ (which makes passes at compilers) written by James Hacker.

signature of Ty Coon, 1 April 1989 Ty Coon, President of ViceThis General Public License does not permit incorporating your program

into proprietary programs. If your program is a subroutine library, you mayconsider it more useful to permit linking proprietary applications with thelibrary. If this is what you want to do, use the GNU Library General PublicLicense instead of this License.

131