Skip to content

large programs

William Wood edited this page Aug 9, 2019 · 5 revisions

Making Large Control Programs

If you are intending on making a full-on control program for a set-up, it is often helpful to stick to some guide-lines in terms of what it should contains and how it's laid-out.

On this page we shall cover these concepts along with an example program which we will write in Kotlin.

Extending GUI Classes

The first thing to understand is the idea of extending the existing GUI classes to create our own, custom, GUI elements. In-particular by using the container classes like Grid and Tabs. In essence this will let you create a window/tab and have it completely defined within its own class thus breaking up your code into manageable chunks.

For example, if we wanted a window that contains a set of elements in a Grid, then we could extend the Grid class like so:

class MyGrid : Grid("My Grid", 2) {

}

In this example, the class MyGrid will be a Grid titled "My Grid" with 2 columns. That is if we were now to create a MyGrid object and call show() on it:

val myGrid = MyGrid()
myGrid.show()

we would get a blank window titled "My Grid", just as if we have created a grid with that name and shown it:

val myGrid = Grid("My Grid", 2)
myGrid.show()

You can then store elements as class variables and then add them to the grid when it is created inside an init { } block like so:

class MyGrid : Grid("My Grid", 2) {

  val plot  = Plot("Plot of Results", "X", "Y")
  val table = Table("Table of Results")

  init {
    addAll(plot, table)
  }

}

Now, MyGrid objects will be a Grid containing a Table and Plot. Basically we have created a new GUI element by combining the standard ones.

Showing it as before would give us:

Instrument Connections

The first tab to create is normally a ConnectorGrid to let the user connect to instruments.

class Connections : ConnectorGrid("Connections") {

}

Now, we want to add InstrumentConfig elements to this Grid to represent each instrument connection that we want to form. For instance, if we want two SMUs, a TC, and a LockIn:

class Connections : Grid("Connections", 2) {

  // Create each element that we want to add to the grid
  val smu1   = addSMU("SMU 1")
  val smu2   = addSMU("SMU 2")
  val tc     = addTC("Temperature Controller")
  val lockIn = addLockIn("Lock-In Amplifier")

}

If we were to create a Connections element now and show() it:

val connections = Connections()
connections.show()

we would get:

Instrument Configurators

The next element you will likely want to create is one to configure which instrument and which channel on which instrument is used for what purpose. As described on the Configurators page, this is done using the various Configurator objects.

We will want to arrange all our Configurator objects in a Grid, so let's create a class that extends Grid and have it add the Configurator objects to itself in its init {...} block. Additionally, each Configurator will need to be supplied with our ConnectorGrid, so we should add that as a constructor argument for our Config element like so:

class Configs(connections: ConnectorGrid) : Grid("Configuration", 2) {

  val sourceDrain = Configurator.SMU("Source-Drain", connections)
  val sourceGate  = Configurator.SMU("Source-Gate", connections)
  val temperature = Configurator.TC("Temperature Control", connections)

  init {
    addAll(sourceDrain, sourceGate, temperature)
  }

}

Creating and showing:

val connections = Connections()
val configs     = Configs(connections)

configs.show()

Logger and Dashboard

You will normally want to monitor the various parameters in your experiment when running. Therefore you will want to have a logger running in the background, periodically logging important quantities and live-plotting them in a convenient dashboard type tab.

To do this, we will need to not just extend Grid by adding elements but also by adding an extra two methods to start and stop the logger: startLog() and stopLog().

class Dashboard : Grid("Dashboard", 3) {

  val sdVPlot = Plot("Source-Drain Voltage")
  val sdIPlot = Plot("Source-Drain Current")
  val sgVPlot = Plot("Source-Gate Voltage")
  val sgIPlot = Plot("Source-Gate Current")
  val tPlot = Plot("Temperature")
  val fPlot = Plot("Frequency")

  val plots = arrayOf(sdVPlot, sdIPlot, sgVPlot, sgIPlot, tPlot, fPlot)

  var rTask = RTask(2000) { t -> }
  var log: ResultTable = ResultList("")

  init {

    addAll(sdVPlot, sdIPlot, sgVPlot, sgIPlot, tPlot, fPlot)

    for (plot in plots) {
        plot.showLegend(false)
    }

  }

  // We will call this function when we want to start a log
  fun startLog(file: String, sdSMU: SMU, sgSMU: SMU, tc: TC, lock: LockIn) {

    // Create a new log file
    log = ResultStream(
      file,
      Col("Time", "s"),
      Col("SDV", "V"),
      Col("SDI", "A"),
      Col("SGV", "V"),
      Col("SGI", "A"),
      Col("T", "K"),
      Col("f", "Hz")
    )

    // Clear all the plots of any old data
    for (plot in plots) {
      plot.clear()
    }

    // Tell the plots to watch the log file
    sdVPlot.watchList(log, 0, 1)
      .showMarkers(false)
      .setColour(Colour.ORANGE)

    sdIPlot.watchList(log, 0, 2)
      .showMarkers(false)
      .setColour(Colour.TEAL)

    sgVPlot.watchList(log, 0, 3)
      .showMarkers(false)
      .setColour(Colour.PURPLE)

    sgIPlot.watchList(log, 0, 4)
      .showMarkers(false)
      .setColour(Colour.RED)

    tPlot.watchList(log, 0, 5)
      .showMarkers(false)
      .setColour(Colour.BLUE)

    fPlot.watchList(log, 0, 6)
      .showMarkers(false)
      .setColour(Colour.CORNFLOWERBLUE)

    // Create a new repeat-task to take log measurements every 2 seconds
    rTask = RTask(2000) { t ->

      log.addData(
          t.getSecFromStart(),
          sdSMU.getVoltage(),
          sdSMU.getCurrent(),
          sgSMU.getVoltage(),
          sgSMU.getCurrent(),
          tc.getTemperature(),
          lock.getFrequency()
      )

    }

    // Start the logger!
    rTask.start()

  }

  fun stopLog() {
    rTask.stop()
    log.finalise()
  }

}

Measurement Objects

In JISA there is a standard "template" for creating measurement code. This is the form of the Measurement class, specifically you can extend Measurement to define your own measurements. When properly defined, a Measurement object can be used like our Conductivity example below:

val smu         = K2450(GPIBAddress(0,25))
val measurement = Conductivity(smu)
val results     = measurement.newResults("outputFile.csv")

measurement.setVoltages(0, 60, 61)
measurement.performMeasurement()
Clone this wiki locally