MIO01 Detailed Description

Please note the following restrictions with MIO01 Revision 00:

  • Do not connect USB Service interface when you want to use analog1 channel!
  • Wifi performance is not ideal

Both issue are fixed in Revision 01 and higher.

Port Reference Table

In case you want to address the services of the MIO01 via IP:Port, you can use the following table to find the correct port number:

Port Function
10000 Analog Input #1
10001 Analog Input #2
10002 Binary I/Os

Binary I/Os

Features

The MIO01 has a binary I/O function block with 4 channels, corresponding to 4 I/O pins.

  • 2 galvanically isolated groups, group1=I/O 1/2, group2=I/O 3/4
  • Each pin can be used as a
    • binary output with read-back
    • binary input
  • Each group may switch the load to ground or to supply
  • Pins are overcurrent protected
  • The input circuit of each pin draws 0.5mA, and supports a configurable fritting function, that draws 10mA for a short moment.
  • Supply voltage of each group may be between 24VDC and 110VDC nominal.
  • Switching capability of each pin is at least 1A.
  • Each pin has a LED that reflects the input state. In case of overcurrent, all 4 LEDs are flashing fast.
  • Configurable output watchdog, to reset outputs in case host application crashed
  • Max. Input Frequency: 50Hz
  • Max. Output Frequency: 50Hz
  • Max. Inductive Load: 20 mH

Binary I/O Groups Principle

Connection

Each binary I/O group has its own connector:

Binary I/O Groups Principle

Pin Symbol Description
1 CI Common Reference voltage for inputs
2 IO1/3 First I/O pin of the group
3 IO2/4 Second I/O pin of the group
4 CO Common Voltage connected to the output switches

Mating connectors for MIO01:

Use Cases

How to Connect the Load

Load may be connected to ground (high side switch) or to supply (low side switch).

Connect Load

Warning Do not leave the CI pin open! If the CI pin is left open, one I/O may affect the read-back level of the other I/O!

Using Inputs

In case you want to use a pin as input, you can select whether the input needs to be driven to high or low. The switching threshold of the input is ~10V with a hysteresis of ~200mV.

Use Inputs

Controlling a DC Motor

This example shows how you can control a DC Motor in both directions:

Warning When changing direction, open the currently closed contacts first, ensure they are open before closing the complementary switch (brake before make)!

DC Motor Application

Mixed Application

This example shows how you can use one pin of a group as input and the other pin as output. It also shows that you can supply each group with a different voltage:

Mixed Application

Using the io4edge API to access the Binary I/Os

First, install the io4edge client library.

Want to have a quick look to the examples? See our Github repository.

First, install the io4edge client library.

Want to have a quick look to the examples? See our Github repository.

Connect to the binary I/O function

To access the binary I/Os, create a Client and save it to the variable c. Pass as address either a service address or an ip address with port. Examples:

  • As a service address: MIO01-1-binaryIoTypeA
  • As an IP/Port: 192.168.201.1:10002

We need this client variable for all further access methods.

import (
  "time"
  log "github.com/sirupsen/logrus"
  binio "github.com/ci4rail/io4edge-client-go/binaryiotypea"
)

func main() {
    c, err := binio.NewClientFromUniversalAddress(address, time.Second)
    if err != nil {
        log.Fatalf("Failed to create binio client: %v\n", err)
    }
}

To access the binary I/Os, create a Client and save it to the variable binio_client. Pass as address either a service address or an ip address with port. Examples:

  • As a service address: MIO01-1-binaryIoTypeA
  • As an IP/Port: 192.168.201.1:10002

We need this client variable for all further access methods.


import io4edge_client.binaryiotypea as binio
import io4edge_client.functionblock as fb

def main():
  binio_client = binio.Client(address)

Configure the binary I/Os

The following attributes can be configured:

  • Output watchdog
  • Input fritting

You don’t need to configure the direction of the channels: As long as you don’t activate the output switch, the channel is an input.

Output Watchdog

By default, the outputs keep their commanded state forever, even if the host program has terminated or crashed.

To ensure that outputs are turned off in such cases, the firmware implements a watchdog functionality. The watchdog can be enabled per pin and the watchdog timeout is configurable (but is the same for all pins).

When you enable the watchdog and set the timeout to 2000, the host must periodically set the switch to “on” within 2 seconds. If it fails to do so, the firmware turns off the output.

Fritting

The current drawn by the input circuitry is 0.5mA. To avoid contact corrosion, it is advisable to draw a higher current from time to time. Therefore, the MIO01 supports an optional fritting pulse that applies a 10mA pulse for 10ms each second.

Apply the configuration

The go API allows to configure each attribute individually, for example:

Set 2 seconds watchdog on output0, but not on other outputs:

    if err := c.UploadConfiguration(binio.WithOutputWatchdog(0x1, 2000)); err != nil {
      log.Fatalf("Failed to set configuration: %v\n", err)
    }

Enable fritting for all inputs:


    if err := c.UploadConfiguration(binio.WithInputFritting(0xf); err != nil {
      log.Fatalf("Failed to set configuration: %v\n", err)
    }

Enable fritting on channel #1, #2, #3 and #4, enable watchdog on channel #1 and #2 and set the watchdog timeout to 2 seconds:

    config = binio.Pb.ConfigurationSet(outputFrittingMask=0xf,
                                       outputWatchdogMask=0x3,
                                       outputWatchdogTimeout=2000)

Controlling Output Switches

The API provides two methods to control output switches

  • Control a single pin
  • Control multiple pins

Control a single pin:

    // switch on binary output #1
    err = c.SetOutput(0, true)

Control multiple pins using a bit mask. The second parameter to SetAllOutputs is a mask that specifies which outputs are affected:

    // switch on binary output #1, turn off output #2, don't change output #3 and #4
    err := c.SetAllOutputs(0x1, 0x3)

    // switch off all outputs
    err := c.SetAllOutputs(0x0, 0xf)

Control a single pin:

    # switch on binary output #1
    binio_client.set_output(0, True)

Control multiple pins using a bit mask. The second parameter to set_all_outputs is a mask that specifies which outputs are affected:

    # switch on binary output #1, turn off output #2, don't change output #3 and #4
    binio_client.set_all_outputs(0x1, 0x3)

    # switch off all outputs
    binio_client.set_all_outputs(0x0, 0xf)

Overcurrent Protection

The outputs are overcurrent protected. In case an overcurrent condition is detected on one output, ALL outputs are disabled. The firmware attempts after some milliseconds to turn on the previously closed switches again, because the overcurrent may come from a capacitive load or from a temporary short circuit.

If after approx. 50ms the overcurrent condition still remains, ALL outputs enter an error state.

  • In the error state, no outputs can be set; inputs can still be read.
  • During error state, all 4 LEDs are flashing fast

Application may clear the error state (in case application knows that the root cause of the error vanished):

    err := c.ExitErrorState()
    binio_client.exit_error_state()

This tells the binary output controller to try again. It does however not wait if the recovery was successful or not.

Reading Inputs

The API provides two methods to read the current state of the pins:

  • Get value of a single pin
  • Get value of multiple pins
    // read state of I/O #1
    // value will be true, if the level is above the input switching threshold
    value, err := c.Input(0)

    // read state of I/O #1 and #2.
    // values contains then a bit mask with the state of each input
    values, err = c.AllInputs(0x3)
    # read state of I/O #1
    # value will be true, if the level is above the input switching threshold
    value = binio_client.input(0)

    # read state of I/O #1 and #2.
    # values contains then a bit mask with the state of each input
    values = binio_client.all_inputs(0x3)

Input Transient Recording

In data logger applications, you may want to record changes of the inputs.

Therefore, the API provides functions to start a Stream. At stream creation, you select the pins which you want to monitor for changes.

The MIO01 samples the channel values at a rate of 100Hz, so the timestamps are accurate to 10ms.

// start stream, watch for changes on I/O #1 and 2
err = c.StartStream(binio.WithChannelFilterMask(0x3))

// alternatively, use the defaults, i.e. watch for changes on all 4 pins
err = c.StartStream()

For each transition, a Sample is generated in the stream, each sample contains:

  • A timestamp of the transition
  • The pin that changed
  • New value of the pin

For efficiency, multiple samples are gathered and sent as one Bucket to the host. To read samples from the stream:

  for {
    // read next bucket from stream
    sd, err := c.ReadStream(time.Second * 1)

    if err != nil {
      log.Errorf("ReadStreamData failed: %v\n", err)
    } else {
      samples := sd.FSData.GetSamples()
      fmt.Printf("got stream data seq=%d ts=%d\n", sd.Sequence, sd.DeliveryTimestamp)

      for i, sample := range samples {
        fmt.Printf("  #%d: ts=%d channel=%d %t\n", i, sample.Timestamp, sample.Channel, sample.Value)
      }
    }
  }
    # start stream, watch for changes on I/O #1 and 2
    binio_client.start_stream(
        binio.Pb.StreamControlStart(channelFilterMask=0x3),
        fb.Pb.StreamControlStart(
            bucketSamples=25,
            keepaliveInterval=1000,
            bufferedSamples=50,
            low_latency_mode=True,
        ),
    )

For each transition, a Sample is generated in the stream, each sample contains:

  • A timestamp of the transition
  • The pin that changed
  • New value of the pin

For efficiency, multiple samples are gathered and sent as one Bucket to the host. To read samples from the stream:

    while True:
        generic_stream_data, stream_data = binio_client.read_stream()
        print(
            f"Received stream data {generic_stream_data.deliveryTimestampUs}, {generic_stream_data.sequence}"
        )
        for sample in stream_data.samples:
            print(" Channel %d -> %d" % (sample.channel, sample.value))

NOTE: At the moment, timestamps are expressed in micro seconds relative to the start of the MIO01. Future client libraries will map the time to the host’s time domain

Controlling the Stream

The stream behavior can be fine-tuned to the application needs. If you do not specify any parameters, the default values are used.

  • The BucketSamples parameter (default: 25) defines the number of samples per bucket. If the bucket contains BucketSamples, it is sent to the client.

  • The KeepAliveInterval parameter (default: 1000) defines the maximum time in ms between two buckets. If the bucket is not full, it is sent after the configured interval.

  • The BufferedSamples parameter (default: 50) defines the number of samples that can be buffered in the device. If the buffer is full, the oldest samples are overwritten. As a rule of thumb, BufferedSamples should be at least two times the BucketSamples. Select a higher number if your reception process is slow to avoid buffer overruns.

  • If you want low latency on the received data, you can enable the “low latency” mode by using LowLatencyMode (default: false). In this mode, samples are sent as soon as possible after they have been received. This means that the buckets contain 1..BufferedSamples samples.

  // configure stream to send the bucket at least once a second
  // configure the maximum samples per bucket to 25
  // configure low latency mode
  // configure the buffered samples to 200
  err = c.StartStream(
      binio.WithFBStreamOption(functionblock.WithKeepaliveInterval(1000)),
      binio.WithFBStreamOption(functionblock.WithBucketSamples(25)),
      binio.WithFBStreamOption(functionblock.WithLowLatencyMode(true))
      binio.WithFBStreamOption(functionblock.WithBufferedSamples(200)),
  )

The stream behavior can be fine-tuned to the application needs:

  • The bucketSamples parameter defines the number of samples per bucket. If the bucket contains bucketSamples, it is sent to the client.

  • The keepAliveInterval parameter defines the maximum time in ms between two buckets. If the bucket is not full, it is sent after the configured interval.

  • The bufferedSamples parameter defines the number of samples that can be buffered in the device. If the buffer is full, the oldest samples are overwritten. As a rule of thumb, bufferedSamples should be at least two times the bucketSamples. Select a higher number if your reception process is slow to avoid buffer overruns.

  • If you want low latency on the received data, you can enable the “low latency” mode by setting low_latency_mode to True. In this mode, samples are sent as soon as possible after they have been received. This means that the buckets contain 1..bufferedSamples samples.

    # start stream, watch for changes on I/O #1 and 2
    binio_client.start_stream(
        binio.Pb.StreamControlStart(channelFilterMask=0x3),
        fb.Pb.StreamControlStart(
            bucketSamples=25,
            keepaliveInterval=1000,
            bufferedSamples=50,
            low_latency_mode=True,
        ),
    )

Multiple Clients

It is possible to have multiple clients. Example usage:

  • Each client has its own stream. One client may have a stream that records transitions on I/O #1, while another client records transitions on I/O #2, #3 and #4.
  • One client is reading the current state of the I/Os, while another client is recording transitions and a third client is writing to an I/O.

Analog Inputs

The MIO01 has two analog in function blocks each with one analog input.

Features

  • Delta-Sigma Converter with 300Hz to 4000Hz sampling frequency
    • up to 1500Hz: with guaranteed timing jitter less than 150µs
    • above 1500Hz: possible higher jitter and sporadic drop of samples
  • Resolution depends on the sampling frequency
    • 15 bit @300Hz
    • 13 bit @1000Hz
    • 11.5 bit @2000Hz
    • 8.8 bit @4000Hz
  • Accuracy: Better than 0.5% to full scale @300Hz sampling rate
  • Voltage or current measurement
    • Voltage measurement range +/-10V. Input impedance 100kOhms/1nF.
    • Current measurement range +/-20mA. Impedance 50Ohms.
  • Each analog input is completely isolated
  • Integrated power supply for sensor: 24V, up to 24mA

Connection

Each analog I/O group has its own connector:

Analog Connector

Pin Symbol Description
1 +24V Sensor supply output
2 Uin Voltage Input
3 Iin Current Input
4 0V Measurement Ground

Mating connectors for MIO01:

Aliasing Considerations

Note that the MIO01 analog inputs do not have anti-aliasing filters. This means that the input signal must be sampled at a rate that is at least twice the highest frequency component of the signal. For example, if the input signal has a 1kHz sine wave, the sampling rate must be at least 2kHz. If the sampling rate is too low, the input signal will be aliased, and the output will be distorted.

Or in other words, your signal must not have frequency components higher than half of the sampling frequency.

Using the io4edge API to access the Analog Inputs

First, install the io4edge client library.

Want to have a quick look to the examples? See our Github repository.

First, install the io4edge client library.

Want to have a quick look to the examples? See our Github repository.

Connect to the Analog Input function

To access the Analog Inputs, create a Client and save it to the variable c. Pass as address either a service address or an ip address with port. Example:

  • As a service address: MIO01-1-analogInTypeA1
  • As an IP/Port: 192.168.201.1:10000

We need this client variable for all further access methods.

import (
  time
  log "github.com/sirupsen/logrus"

  anain "github.com/ci4rail/io4edge-client-go/analogintypea"
  "github.com/ci4rail/io4edge-client-go/functionblock"
)

func main() {
    c, err := anain.NewClientFromUniversalAddress("S101-IUO01-USB-EXT-1-analogInTypeA1", time.Second)
    if err != nil {
        log.Fatalf("Failed to create anain client: %v\n", err)
    }
}

To access the analog inputs, create a Client and save it to the variable ana_client. Pass as address either a service address or an ip address with port. Examples:

  • As a service address: MIO01-1-analogInTypeA1
  • As an IP/Port: 192.168.201.1:10000

We need this client variable for all further access methods.


import io4edge_client.analogintypea as ana
import io4edge_client.functionblock as fb

def main():
  ana_client = ana.Client(args.addr)

Set the Sampling Rate

The sample rate is by default set to 300Hz. To change it to a higher value, use

  // set sampleRate to 1000 Hz
  if err := c.UploadConfiguration(anain.WithSampleRate(uint32(1000))); err != nil {
    log.Fatalf("Failed to set configuration: %v\n", err)
  }
    # configure the function block
    config = ana.Pb.ConfigurationSet(sample_rate=1000)
    ana_client.upload_configuration(config)

This setting remains active until you change it again or restart the device.

Reading Input Value

To read the current value of the analog input:

  val, err := c.Value()
    val = ana_client.value()

Where val is a floating point number reflecting the current value, from -1.0..+1.0, corresponding to the full scale.

For voltage measurement, a value of -1.0 corresponds to -10V, and +1.0 corresponds to +10V.

For current measurement, a value of -1.0 corresponds to -20mA, and +1.0 corresponds to +20mA.

Note that the value is updated internally with the configured sample rate. If high accuracy is required, set the sample rate to the lowest sample rate (300Hz).

Streamed Sampling

In data logger applications, you may want to record the waveform on the analog input.

Therefore, the API provides functions to start a Stream.

// start stream
err = c.StartStream()
    # start stream
    ana_client.start_stream(
        fb.Pb.StreamControlStart(
            bucketSamples=100,
            keepaliveInterval=1000,
            bufferedSamples=200,
            low_latency_mode=False,
        ),
    )

Then, Samples are generated with the configured sample rate in the stream, each sample contains:

  • A timestamp of the sample
  • Measured value

For efficiency, multiple samples are gathered are sent as one Bucket to the host. To read samples from the stream:

  for {
    // read next bucket from stream
    sd, err := c.ReadStream(time.Second * 1)

    if err != nil {
      log.Errorf("ReadStreamData failed: %v\n", err)
    } else {
      samples := sd.FSData.GetSamples()
      fmt.Printf("got stream data seq=%d ts=%d\n", sd.Sequence, sd.DeliveryTimestamp)

      for i, sample := range samples {
        fmt.Printf("  #%d: ts=%d %.4f\n", i, sample.Timestamp, sample.Value)
      }
    }
  }
    n = 0
    while True:
        generic_stream_data, stream_data = ana_client.read_stream()
        print(
            f"Received stream data {generic_stream_data.deliveryTimestampUs}, {generic_stream_data.sequence}"
        )
        for sample in stream_data.samples:
            print(" #%d: ts=%d %.4f" % (n, sample.timestamp, sample.value))
            n += 1

sample.Value is as described in the section above

NOTE: At the moment, timestamps are expressed in micro seconds relative to the start of the MIO01. Future client libraries will map the time to the host’s time domain

Controlling the Stream

The stream behavior can be fine-tuned to the application needs. If you do not specify any parameters, the default values are used.

  • The BucketSamples parameter (default: 25) defines the number of samples per bucket. If the bucket contains BucketSamples, it is sent to the client.

  • The KeepAliveInterval parameter (default: 1000) defines the maximum time in ms between two buckets. If the bucket is not full, it is sent after the configured interval.

  • The BufferedSamples parameter (default: 50) defines the number of samples that can be buffered in the device. If the buffer is full, the oldest samples are overwritten. As a rule of thumb, BufferedSamples should be at least two times the BucketSamples. Select a higher number if your reception process is slow to avoid buffer overruns.

  // configure stream to send the bucket at least once a second
  // configure the samples per bucket to 100
  // configure the buffered samples to 200
  err = c.StartStream(
    functionblock.WithKeepAliveInterval(1000),
    functionblock.WithBucketSamples(100),
    functionblock.WithBufferedSamples(200),
  )

The stream behavior can be fine-tuned to the application needs:

  • The bucketSamples parameter defines the number of samples per bucket. If the bucket contains bucketSamples, it is sent to the client.

  • The keepAliveInterval parameter defines the maximum time in ms between two buckets. If the bucket is not full, it is sent after the configured interval.

  • The bufferedSamples parameter defines the number of samples that can be buffered in the device. If the buffer is full, the oldest samples are overwritten. As a rule of thumb, bufferedSamples should be at least two times the bucketSamples. Select a higher number if your reception process is slow to avoid buffer overruns.

    # start stream
    ana_client.start_stream(
        fb.Pb.StreamControlStart(
            bucketSamples=100,
            keepaliveInterval=1000,
            bufferedSamples=200,
            low_latency_mode=False,
        ),
    )

Multiple Clients

It is possible to have multiple clients active at the same time. For example: One client reads the current value of the analog channel, another client reads the stream

Note that all clients use the same sampling rate on a particular analog channel.