A Fictional Use Case With Kvaser DIN Rail and t programming

  • February 25, 2020
  • Magnus Carlsson

In order to create a fictional use case, let us assume that the company we are working for are installing a dangerous machine on the factory floor. The machine is surrounded by a fence with access to the machine through a single gate, see Figure 2.

Figure 2: Overview of installation with fence, gate sensor, and light

A sensor attached to the gate monitors if the gate is opened or closed. Two warning lights have also been set up, one green and one red, these lights have two separate control wires, one for the green light and one for the red light. To turn on a light we apply a high value, to turn off the light, a low value should be applied. It is now our job to make sure that the red light is lit up whenever the gate is opened, and otherwise the green light should be lit, showing that the gate is closed. We should also send a CAN event message to our central control unit whenever the status of the gate changes. The system should also periodically send a CAN status message containing the current state of both the lights and the gate to a central control unit.

Figure 3: Showing the system with gate open, including Kvaser DIN Rail

The central control unit should also have the possibility to send a CAN message which sets the system in test mode for a specified period of time. The periodically sent CAN status messages, as well as the CAN event messages, should both contain information showing if the system is in test mode or not. When the test mode is active, the two lights should do a fast alternation between red and green.

Creating a DBC Database

Since we should send and receive CAN messages, let us start by creating a database (DBC file) defining the CAN messages we are going to use. All signals are defined as “Unsigned” with byteorder “Intel” and bit length of 8. No scaling is applied so “Factor” is set to one, and “Offset” is set to zero. See Table 1 for an overview of our signals.

Table 1: Overview of the signals and messages we have defined for our system.

Connecting the Wires

At our disposal, we have a Kvaser DIN Rail connected to one digital add-on module with 16 digital outputs and 16 digital inputs. Connecting the gate sensor to a digital input, marked as DI1 on the housing, and the green and red light control wires to two digital outputs, marked as DO3 and DO8 makes this a perfect match, see Figure 4.

As could be seen in the database signals, and also by looking at the schematics in Figure 4, the gate sensor is actually indicating a closed gate with a low value (zero) and an open gate with a high value (one), this is accomplished by using the pull-up resistor R3 in Figure 4.1

I/O functions in the t programming language use a pin number to access any pin on the connected I/O add-on modules. Available pins are given consecutive integer numbers starting with 0 for the first pin on the first module, 1 for the second pin on the first module, and so on until the last pin on the last module has been enumerated.

Taking a look in the Kvaser DIN Rail User’s Guide and using the table shown in Figure 5, we can now define constants for our pins:

  const int GATE_PIN = 16;            // labeled DI1
  const int GREEN_LIGHT_PIN = 2;      // labeled DO3
  const int RED_LIGHT_PIN = 7;        // labeled DO8

Figure 4: Schematics over our gate sensor and red and green light

Figure 5: Digital add-on pin enumeration taken from the Kvaser DIN Rail User’s Guide.

The structure of our t program

Before jumping into the details of our t program, let us first take a bird’s-eye view of a skeleton of our upcoming t program. For more information about the anatomy of a t program, please read the Kvaser t Programming Language2.

// The first section is the variables section, which contains definitions of 
// global variables, constants, and types
variables {
  const int GATE_PIN = 16;            // labeled DI1  
  const int GREEN_LIGHT_PIN = 2;      // labeled DO3
  const int RED_LIGHT_PIN = 7;        // labeled DO8
...
}

// Next we place all our upcoming function definitions
int getConfig(int config[])
{
  ... 
}
...

// The next section contains all our event hooks, i.e. pieces of code that
// response to specific events.
// The first event hook is the on Timer hook, this code is run when the timer 
// specified by the timer argument expires
on Timer statusMessageTimer {
  ...
}
...

// The on IoEvent code block is executed when the specified I/O event arrives.
on IoEvent <0> kvIO_EVENT_CONFIG_CHANGED {
  ...
}
...

// The on CanMessage code block is executed when the specified CAN message 
// arrives.
on CanMessage SetMode {
  ...
}

// The on start code block is run when program execution starts.
on start {
  printf("---------------- t program started -----------------\n");
  ...
}

// The on stop code block is run when program execution is stopped.
on stop {
  ...
}

Listing 5: The bird’s-eye view of the structure of our upcoming t program.

Confirming the Configuration

Before being able to access the I/O pins through reading and writing, we need to confirm the configuration by calling the function kvIoConfirmConfig(). This step is a required safety precaution in order to prevent our program to tinker with I/O pins if someone downloads the wrong program to the wrong Kvaser DIN Rail, controlling something completely different. It is possible to e.g. compare the serial numbers of the add-on modules to a list of known serial numbers, but in our example we are content with just verifying the number and types of add-on modules connected.

In order to confirm the I/O configuration, we first find out what add-on modules that are currently connected to our Kvaser DIN Rail. This is done in a getConfig function, as shown in Listing 6. The getConfig function fills in the module type it finds in the supplied config[] argument.

int getConfig(int config[])
// Fill the config array with the module type of connected add-on modules
{
  int moduleType;
  int status;
  int pinCount;

  printf("Reading I/O Configuration\n");
  // get the total number of pins connected to our Kvaser DIN Rail
  status = kvIoGetNumberOfPins(&pinCount);
  if (status) {
    printf("ERROR: kvIoGetNumberOfPins failed with status %d\n", status);
    return status;
  }
  printf("In total %d I/O pins was found\n", pinCount);
  if (pinCount < 1) {
    return 0;
  }
  int pinIndex = 0;
  int moduleIndex = 0;
  // loop through the first pin of every connected add-on module, finding out
  // what add-on modules that are connected
  do {
    status = kvIoPinGetInfo(pinIndex, kvIO_INFO_GET_MODULE_TYPE, &moduleType);
    if (status) {
      printf("ERROR: kvIoGetNumberOfPins failed with status %d\n", status);
      return status;
    }
    // save the type of the add-on module found
    config[moduleIndex] = moduleType;
    // find out how many pins this type of add-on module has
    int modulePinCount = getModulePinCount(moduleType);
    printf("Module type %d at pin %d, has %d pins\n", moduleType, pinIndex, modulePinCount);
    // increase the pinIndex to point at the first pin after the current module
    pinIndex += modulePinCount;
    moduleIndex++;
  } while (pinIndex < pinCount);
}

Listing 6: Function that fills an array with the module types found of all connected add-on modules.

In order not to have to loop through all pins, we called getModulePinCount(moduleType) in Listing 6. This function simply returns the number of pins a specific type of add-on module has, see Listing 7.

int getModulePinCount(int moduleType)
// Return the number of pins that a specific module type has.
{
  // default pin count used for unknown modules
  int pinCount = 100;
  switch (moduleType) {

  case kvIO_MODULE_TYPE_DIGITAL:
    printf("Digital module found.\n");
    pinCount = 32;  // 16 Digital Outputs, 16 Digital Inputs
    break;

  case kvIO_MODULE_TYPE_ANALOG:
    printf("Analog module found.\n");
    pinCount = 8;  // 4 Analog Outputs, 4 Analog Inputs
    break;

  case kvIO_MODULE_TYPE_RELAY:
    printf("Relay module found.\n");
    pinCount = 16;  // 8 Relay Outputs, 8 Digital Outputs
    break;

  default:
    printf("ERROR: Unknown module type: %d, assuming it has %d pins.\n", moduleType, pinCount);
    break;
  }
  return pinCount;
}

Listing 7: Helper function that returns the number of I/O pins a specific add-on module type has.

We also need to specify what add-on modules we are expecting. Let’s create a new function, see Listing 8, that holds the specification of expected add-on modules, as well as calls the previous getConfig function in order to read out the actual add-on modules connected to our Kvaser DIN Rail.

void verifyAndConfirmConfig(void)
// Verify that the connected add-on modules are the ones we expected
{
  // define what I/O modules the program expects, we expect a single Digital
  // add-on module
  int expectedConfig[4] = {
    kvIO_MODULE_TYPE_DIGITAL,
    0,
    0,
    0
  };

  // read out the connected I/O modules
  int config[4] = {0, 0, 0, 0};
  getConfig(config);

  // compare if the list of conneted modules is equal to our expectation
  int result = compareArray(config, expectedConfig);
  if (result) {
    printf("ERROR: Unexpected Config (differ in position %d), alert user...\n", result - 1);
    printf("       %d vs %d\n", config[result], expectedConfig[result]);
    // stop running by triggering an exeption ;-)
    int dummy = 1 / 0;
  } else {
    // the configuration was as expected, confirm I/O config and continue
    // running...
    int status = kvIoConfirmConfig();
    if (status) {
      printf("ERROR: kvIoConfirmConfig failed with status %d\n", status);
    }
  }
}

Listing 8: Function that verifies and confirms the configuration if we have one single Digital add-on connected.

The last piece in the configuration confirmation puzzle is the compareArray() function we used in Listing 8 to do the actual comparison between expected and found module types, see Listing 9.

int compareArray(int arrayA[], int arrayB[])
// Compares two arrays, returns 0 if they are equal,
// returns -1 if length differs,
// else returns the first index (starting at 1) that differ
{
  if (arrayA.count != arrayB.count) {
    return -1;
  }
  for (int i = 0 ; i < arrayA.count ; i++) {
    if (arrayA[i] != arrayB[i]) {
        return i + 1;
    }
  }
  return 0;
}

Listing 9: Function that compares to arrays and returns the first position that differs, or -1 if the length differs.

We can now call verifyAndConfirmConfig() from inside the on start hook, but there’s one caveat. If we want to start our t program at power-on, the current version of the FW (v3.16) has not yet found any I/O modules when the on start hook is executed so this will fail. The workaround is to also place the configuration confirmation inside a kvIO_EVENT_CONFIG_CHANGED hook as shown in Listing 10. The kvIO_EVENT_CONFIG_CHANGED hook will run whenever an add-on module is added or removed, as well as during power on.

on IoEvent <0> kvIO_EVENT_CONFIG_CHANGED {
  verifyAndConfirmConfig();
  setLight(LIGHT_BOTH);
}

Listing 10: Placing call to verifyAndConfirmConfig() inside the “on IoEvent” makes the verifying code run whenever needed, including at boot up.

Here we also caught a glimpse of the setLight() function which we will implement next in order to control the lights.

Make the Lights Turn On and Off

When the I/O configuration has been confirmed, we may read and set our I/O ports using kvIoPinSetDigital(). To make it easier to read our code, we first define a number of constants specifying the different states our two lights can be set to.

  // constants used in the setLight() function
  const int LIGHT_OFF = 0;
  const int LIGHT_GREEN = 1;
  const int LIGHT_RED = 2;
  const int LIGHT_BOTH = 3;

We then create the setLight() function, see Listing 11, that will set the digital I/O pins as needed, in order to turn the green and red light on and off as indicated by the incoming parameter.

void setLight(int lightSetting)
// Set green and red light according to incoming argument.
{
  int status;
  int greenLightValue = 0;
  int redLightValue = 0;

  switch (lightSetting) {

    case LIGHT_OFF:
      greenLightValue = 0;
      redLightValue = 0;
      break;
    case LIGHT_GREEN:
      greenLightValue = 1;
      redLightValue = 0;
      break;
    case LIGHT_RED:
      greenLightValue = 0;
      redLightValue = 1;
      break;
    case LIGHT_BOTH:
      greenLightValue = 1;
      redLightValue = 1;
      break;
    default:
      printf("ERROR: Unknown lightSetting: %d\n", lightSetting);
  }

  // set green light to correct state
  status = kvIoPinSetDigital(GREEN_LIGHT_PIN, greenLightValue);
  if (status) {
    printf("ERROR: kvIoPinSetDigital for pin %d failed with status %d\n", GREEN_LIGHT_PIN, status);
  }

  // set red light to correct state
  status = kvIoPinSetDigital(RED_LIGHT_PIN, redLightValue);
  if (status) {
    printf("ERROR: kvIoPinSetDigital for pin %d failed with status %d\n", RED_LIGHT_PIN, status);
  }
}

Listing 11: Function to set the green and red lights according to the input argument.

Sending CAN Status Message Periodically

One of our requirements was to send a CAN status message peridoically. In our DBC file, we defined this CAN message as Event, containing the status of the gate and our two lights in the signals named GateStatus, GreenLight, and RedLight.

Before sending or receiving any CAN messages, we need to set the bus parameters and go bus on, which we will do inside the on start hook.

 // Initialize CAN bus
  canSetBusParams(canBITRATE_1M, 0, 0, 0, 0);
  canBusOn();

We can now create a function that reads the current status of the gate and the two lights, as well as information about the test mode (more about that later). The function then uses the predefined type CanMessage_Status in order to send the information as specified in the DBC file.

void sendStatusMsg()
// Send current status of gate and lights in CAN message
{
  int status;
  int gateValue;
  int greenValue;
  int redValue;
  CanMessage_Status canStatusMsg;  // "Status" is a Message defined in our DBC file

  // read value of gate pin using kvIoPinGetDigital
  status = kvIoPinGetDigital(GATE_PIN, &gateValue);
  if (status) {
    printf("ERROR: kvIoPinGetDigital for pin %d failed with status %d\n", GATE_PIN, status);
  }

  // the light pins are Digital Outputs, so we need to use kvIoPinGetOutputDigital
  // to read the latest set value
  status = kvIoPinGetOutputDigital(GREEN_LIGHT_PIN, &greenValue);
  if (status) {
    printf("ERROR: kvIoPinGetDigital for pin %d failed with status %d\n", GREEN_LIGHT_PIN, status);
  }

  // read latest set value for red light
  status = kvIoPinGetOutputDigital(RED_LIGHT_PIN, &redValue);
  if (status) {
    printf("ERROR: kvIoPinGetDigital for pin %d failed with status %d\n", RED_LIGHT_PIN, status);
  }

  printf("Periodic values test_counter: %d, gate: %d, green: %d, red: %d\n", testModeCounter, gateValue, greenValue, redValue);

  // fill in the read values in the corresponding signal, as defined in our
  // DBC file
  canStatusMsg.ModeCycles.Phys = testModeCounter;
  canStatusMsg.GateStatus.Phys = gateValue;
  canStatusMsg.GreenLight.Phys = greenValue;
  canStatusMsg.RedLight.Phys = redValue;

  // send CAN message
  status = canWrite(canStatusMsg);
  if (status) {
    printf("ERROR: canWrite in sendStatusMsg failed with status %d\n", status);
  }
}

Listing 12: Function to send current value of Digital Input and Digital Output as a CAN message using predefined signals in a DBC file.

The CAN status message should be sent regularly, so we set up a timer, statusMessageTimer, to handle this. The initialization is done in the on start hook.

  // initialize status message timer
  statusMessageTimer.timeout = 5000;  // timer cycle in ms
  timerStart(statusMessageTimer, FOREVER);

The on Timer hook, which will trigger every 5 seconds, can now regularly send a CAN status message, see Listing 13.

on Timer statusMessageTimer {
  int status;
  int gateValue;

  printf("statusMessageTimer triggered\n");

  // send status message with current values
  sendStatusMsg();

  // now set the LED lights according to value of gate sensor,
  // just to be on the safe side
  setLightAccordingToGateState();
}

Listing 13: Timer handler to send CAN status messages regularly.

The function to set the lights according to the state of the gate sensor is shown in Listing 14 (Please be patient, we will get to the testModeCounter shortly).

void setLightAccordingToGateState(void)
{
  int status;
  int gateValue;

  // read value of gate pin
  status = kvIoPinGetDigital(GATE_PIN, &gateValue);
  if (status) {
    printf("ERROR: kvIoPinGetDigital for pin %d failed with status %d\n", GATE_PIN, status);
  }

  if (testModeCounter) {
    printf("Suppressed led change, test mode running (setLight:%d)\n", gateValue);
  } else {
    if (gateValue) {
      setLight(LIGHT_RED);
    } else {
      setLight(LIGHT_GREEN);
    }
  }
}

Listing 14: Reading state of gate and set lights accordingly.

Detecting Changes from the Gate Sensor

There are two hooks available for detecting changes on a Digital Input pin, kvIO_EVENT_RISING_EDGE and kvIO_EVENT_FALLING_EDGE. When we detect a rising edge on the gate sensor, the gate has been opened and we should turn on the red light. (The testModeCounter will be explained later.)

on IoEvent <GATE_PIN> kvIO_EVENT_FALLING_EDGE {
  int status;
  CanMessage_Event canEventMsg;

  // the "this.value" currenly always returns a float, so we need to cast here
  printf("gate sensor (pin %d) fell to %d\n", this.pin, (int)this.value);
  if (testModeCounter) {
    printf("Suppressed setting green led, test mode running.\n");
    canEventMsg.Mode.Phys = 1;
  } else {
    setLight(LIGHT_GREEN);
    canEventMsg.Mode.Phys = 0;
  }
  canEventMsg.GateEvent.Phys = (int)this.value;

  // send CAN message
  status = canWrite(canEventMsg);
  if (status) {
    printf("ERROR: canWrite in rising event failed with status %d\n", status);
  }
}

Listing 15: When a rising edge is detected on the gate sensor, turn on the red light and send CAN event message.

Similarly, we should light up the green light and also send a CAN event message when a falling edge is detected on the gate sensor.

on IoEvent <GATE_PIN> kvIO_EVENT_RISING_EDGE {
  int status;
  CanMessage_Event canEventMsg;

  // the "this.value" currenly always returns a float, so we need to cast here
  printf("gate sensor (pin %d) rised to %d\n", this.pin, (int)this.value);
  if (testModeCounter) {
    printf("Suppressed setting red led, test mode running.\n");
    canEventMsg.Mode.Phys = 1;
  } else {
    setLight(LIGHT_RED);
    canEventMsg.Mode.Phys = 0;
  }
  canEventMsg.GateEvent.Phys = (int)this.value;

  // send CAN message
  status = canWrite(canEventMsg);
  if (status) {
    printf("ERROR: canWrite in falling event failed with status %d\n", status);
  }
}

Listing 16: When a falling edge is detected on the gate sensor, turn on the green light and send CAN event message.

The Test Mode

We have seen the testModeCounter appear earlier and it is finally time to explain the usage of this global variable.

The central control unit should have the possibility to set our system into a test mode for a number of seconds as specified in the SetMode CAN message as we previously defined in our DBC file. During the test mode, the lights should alternate between red and green in fast succession.

We start by adding an on CanMessage hook for the SetMode CAN message. If the Mode signal in the SetMode CAN message is one, we enter test mode, if the Mode signal is zero, we immediately exit test mode.

on CanMessage SetMode {
  printf("SetMode(%d, %d cycles) received\n", this.Mode.Phys, this.Duration.Phys);
  if (this.Mode.Phys) {
    enterTestMode(this.Duration.Phys);
  } else {
    exitTestMode();
  }
}

Listing 17: When a SetMode CAN message is received we start test mode

When test mode is entered, we setup a timer to trigger when it’s time to switch the light from one color to the other. Since we switch the light every 250 ms, the number of timer cycles we should stay in test mode is the duration in seconds times four. Our global variable testModeCounter thus contains the number of cycles left before we should leave test mode, and if the value is zero, we are currently not in test mode.

void enterTestMode(int duration_s)
// Enter test mode and schedule to exit after duration_s seconds.
{
  int cycles;

  testModeTimer.timeout = 250;  // timer cycle in ms
  cycles = duration_s * 4;

  printf("Starting LED test for %d cycles (%d s)\n", cycles, duration_s);
  testModeCounter = cycles;
  timerStart(testModeTimer);
}

Listing 18: Calculating the duration of the test mode, and setting the timer accordingly.

When leaving the test mode, we reset the testModeCounter, in case we leave early, and set the lights according to the current state of the gate sensor.

void exitTestMode()
{
  testModeCounter = 0;
  timerCancel(testModeTimer);
  setLightAccordingToGateState();
}

Listing 19: When a SetMode CAN message is received we start test mode

The actual switching between green and red light is done in the on Timer hook of our testModeTimer. We also decrease the testModeCounter and exits test mode if the testModeCounter reaches zero.

on Timer testModeTimer {
  testModeCounter--;
  printf("testModeTimer triggered (%d)", testModeCounter);

  if (testModeCounter > 0) {
    if (testModeCounter % 2) {
      setLight(LIGHT_GREEN);
    } else {
      setLight(LIGHT_RED);
    }
    timerStart(testModeTimer);
  } else {
    timerCancel(testModeTimer);
  }
}

Listing 20: A timer hook takes care of switching the lights and decreasing the test mode counter.

Clean up

All we have to do now in our t program is to do some clean up in the on stop section.

on stop {
  timerCancel(statusMessageTimer);
  timerCancel(testModeTimer);
  setLight(LIGHT_OFF);    // FW v3.17 is needed for this
  printf("status_timer_dbc.t stopped\n");

  canBusOff();
}

Listing 21: Cleaning up in the “on stop” section.

Conclusion

In this rather lengthy article we have seen how to handle some key parts in the way we write t programs for the Kvaser DIN Rail:

• Confirming a configuration
• Controlling Digital Outputs
• Acting when a Digital Input changes
• Sending CAN messages
• Trigger actions on incoming CAN messages

Hopefully you can use these parts in order to create your own custom t program, solving your unique problem. If you have any questions or comments, please send them to [email protected].

Footnotes

1The reason for having a high value indicating an open gate is that if the cable to the sensor is cut, we will get a high value, and it will still be interpreted as the hazardous state, i.e. the same as an open gate.

2The document The Kvaser t Programming language is available for download from https://www.kvaser.com/download/

Author Image

Magnus Carlsson

Magnus Carlsson is a Software Developer for Kvaser AB and has developed firmware and software for Kv...