Save time and money on vehicle testing using Kvaser’s t programs

4 weeks ago by Vanessa Knivett

Let’s face it, propose a solution to the average vehicle test engineer that saves time and money but involves hard-core C programming and most would say ‘no thanks’. Even for those with the time and inclination to develop complex programs, this isn’t their core skillset.

However, programming on a simple level is a great way of getting more ‘bang for your buck’ from your test devices. The question is: just how simple can ‘simple’ be? There are plenty of complex, feature-rich development environments available for companies with a sizeable software team and plenty of time. There’s few options, though, for small, resource-constrained test teams, or indeed, large companies who need solutions quickly.


Kvaser TRX for vehicle testing

Kvaser has met this need with a free-of-charge development environment called TRX that enables Kvaser’s Pro-level interfaces and dataloggers, such as the Kvaser Memorator Pro 2xHS v2 or the Kvaser USBcan Pro 2xHS v2, to be programmed. TRX allows you to create, debug and interact with t programs. t programs are event-driven C-like programs that can be run in TRX on a PC or flashed to the SD card in a datalogger and run on the device at power-up. 

“The logger worked exactly as needed … it was much easier than the options we had in the past.”

When we say ‘C-like’, we don’t intend that users learn C in order to get started. Far from it. What sets TRX apart is its ease-of-use: start by copying one of the example programs and customizing the parameters to suit your needs; or, if you have a programming background, you can use its many standard programming features to create a custom program from the ground up. A sample program library is available within TRX, and there’s help to get started.

TRX  provides an economical and feature-rich ‘way in’ to seeing and simulating CAN conditions. It is a lightweight integrated development environment (IDE), and as such, won’t swamp your system like some mainstream IDEs.

This makes writing a quick t program on a small portable notebook entirely possible, not least because the ability to execute at the hardware level means that the results won’t be affected by the PC OS’ timing. Other advantages include quality of life (QoL) features that you might not expect from a free software resource, such as automatic bug detection and a built-in project management window for projects with multiple .t files. 

t can be used for a multitude of applications: to control up to five CAN buses; create filters to screen out certain messages or to inject faults; to simulate or interrogate an ECU; make a gateway or bridge between two CAN networks … the list is limited only by the user’s imagination.


Where do I start?

Are there any prerequisites, apart from a PC using Windows and a Kvaser Pro-level interface or datalogger? Not many. You’ll need to download Kvaser CANlib SDK – TRX can be found within it. Whilst programming experience certainly helps, it is not essential. Users will need a logical mindset though! Notably, Kvaser CANLIB SDK includes everything you need to develop software for any Kvaser CAN interface or datalogger.


Success with small and large customers alike

Example 1: A large customer had an intermittent problem on a drivetrain bus. The suspected cause was a faulty multipacket broadcast data transfer (BAM), with packets being received out of sequence. An engineer spent a few days in a truck along with the driver, trying to catch a trace of the problem, before trying out a Kvaser Memorator Pro and TRX.

Said the customer: “I wrote a quick data logging routine that checks for mis-matched BAM packets on the CAN traffic, and when seen, saves data to a buffer.”

With a little help from Kvaser Support, the customer confirmed: “The logger worked exactly as needed … it was much easier than the options we had in the past.”

Example 2: A small company used TRX to develop a t program to replace the vehicle under test, a recreational vehicle in this case, that they had rented for testing purposes but were about to lose.

Specifically, their t program needed to highlight the on/off state of a light when testing a command sent from an iPad. “Can we create a device to monitor the CAN bus and send a specific message when we receive a command?”, they asked. Kvaser helped provide the code for this project, which can be shared and is available here.

Download Example t script >>


/*
** This program will test the dimmer response per the RV-C spec..
** By Bryan Hennessy
** -----------------------------------------------------------------------------------------
*/

variables {
   Timer msgTimer01;  //Define the variable that will be used in the timer. See t-Programming manual 2.10.2.4 for a full explanation on timers.
   Timer msgTimer02;  //Do the same for the rest of the timers
   Timer msgTimer03;
   Timer msgTimer11;
   const int can_channel = 0;
   const int can_bitrate = 250000;
   const int Toggle01 = 0x1FEDB9F; //This is the toggle message with an SA of 9F.
   byte status01_byte[8] = {0x01, 0xFF, 0x00, 0xFC, 0xFF, 0x12, 0x00, 0xFE}; //This is the data to broadcast and respond to a toggle command with.
   byte status02_byte[8] = {0x02, 0xFF, 0x00, 0xFC, 0xFF, 0x12, 0x00, 0xFE}; //This is the data to broadcast and respond to a toggle command with.
   byte status03_byte[8] = {0x03, 0xFF, 0x00, 0xFC, 0xFF, 0x12, 0x00, 0xFE}; //This is the data to broadcast and respond to a toggle command with.
   byte status11_byte[8] = {0x11, 0xFF, 0x00, 0xFC, 0xFF, 0x12, 0x00, 0xFE}; //This is the data to broadcast and respond to a toggle command with.
   int value = 0;
   byte instance;
}

on start {

   canSetBitrate(can_bitrate);
   canBusOn();
   
   msgTimer01.timeout = 2000; //Here we set the timer to count down 2000 msec, or two seconds, before it expires.
   timerStart(msgTimer01, FOREVER); //Next we start the timer and set it to FOREVER so it reloads and starts again. Without the FOREVER it would only run once.
   
   msgTimer02.timeout = 2000; //Here we set the timer to count down 2000 msec, or two seconds, before it expires.
   timerStart(msgTimer02, FOREVER); //Next we start the timer and set it to FOREVER so it reloads and starts again. Without the FOREVER it would only run once.
   
   msgTimer03.timeout = 2000; //Here we set the timer to count down 2000 msec, or two seconds, before it expires.
   timerStart(msgTimer03, FOREVER); //Next we start the timer and set it to FOREVER so it reloads and starts again. Without the FOREVER it would only run once.
   
   msgTimer11.timeout = 2000; //Here we set the timer to count down 2000 msec, or two seconds, before it expires.
   timerStart(msgTimer11, FOREVER); //Next we start the timer and set it to FOREVER so it reloads and starts again. Without the FOREVER it would only run once.
}
/*
** Timer routine, sent with a fixed CAN ID = 0x19FEDA01 that gives Pri=6, PGN=1FEDA, SA =01
** You can change this message ID to anything you want as it's fixed in the msg.id statement below.
*/
on Timer msgTimer01 {
//  printf("%02x %02x %02x %02x %02x %02x %02x %02x", status01_byte[0], status01_byte[1], status01_byte[2], status01_byte[3], status01_byte[4], status01_byte[5], status01_byte[6], status01_byte[7]);
  CanMessage msg;
  msg.flags = canMSG_EXT;
  msg.id = 0x19FEDA01; //gives Priority=6, PGN=1FEDA, SA =01
  msg.dlc = 8;

  for(int value = 0; value < 8; value++) {
    msg.data[value] = status01_byte[value];  //This loop transfers the data from status_byte[x] to the message to be put on the bus.
  }
  canWrite(msg); 
}

on Timer msgTimer02 {
//  printf("%02x %02x %02x %02x %02x %02x %02x %02x", status02_byte[0], status02_byte[1], status02_byte[2], status02_byte[3], status02_byte[4], status02_byte[5], status02_byte[6], status02_byte[7]);
  CanMessage msg;
  msg.flags = canMSG_EXT;
  msg.id = 0x19FEDA01; //gives Priority=6, PGN=1FEDA, SA =01
  msg.dlc = 8;

  for(int value = 0; value < 8; value++) {
    msg.data[value] = status02_byte[value];  //This loop transfers the data from status_byte[x] to the message to be put on the bus.
  }
  canWrite(msg); 
}
on Timer msgTimer03 {
//  printf("%02x %02x %02x %02x %02x %02x %02x %02x", status03_byte[0], status03_byte[1], status03_byte[2], status03_byte[3], status03_byte[4], status03_byte[5], status03_byte[6], status03_byte[7]);
  CanMessage msg;
  msg.flags = canMSG_EXT;
  msg.id = 0x19FEDA01; //gives Priority=6, PGN=1FEDA, SA =01
  msg.dlc = 8;

  for(int value = 0; value < 8; value++) {
    msg.data[value] = status03_byte[value];  //This loop transfers the data from status_byte[x] to the message to be put on the bus.
  }
  canWrite(msg); 
}
on Timer msgTimer11 {
//  printf("%02x %02x %02x %02x %02x %02x %02x %02x", status11_byte[0], status11_byte[1], status11_byte[2], status11_byte[3], status11_byte[4], status11_byte[5], status11_byte[6], status11_byte[7]);
  CanMessage msg;
  msg.flags = canMSG_EXT;
  msg.id = 0x19FEDA01; //gives Priority=6, PGN=1FEDA, SA =01
  msg.dlc = 8;

  for(int value = 0; value < 8; value++) {
    msg.data[value] = status11_byte[value];  //This loop transfers the data from status_byte[x] to the message to be put on the bus.
  }
  canWrite(msg); 
}

on CanMessage<0> (Toggle01)x {
// If message received Toggle the light and send status
  
// if..... how do you ack on the data you receive? Check Data byte 0 and toggle that status_byte on and off
// The RV-C DBC file defines this byte as RVC_DIM_STATUS_INST
CanMessage_RVC_DIM_CMD_2 Command_Message; //The DBC file defines RVC_DIM_CMD_2 and we're going to use some of the signals in that message.
instance = Command_Message.RVC_DIM_CMD_INST.Raw; //This should put the first byte of RVC_DIm_CMD_2 in the byte "instance".
printf("We got a Toggle command for Instance %02x", instance); //This should print a message with the instance received in the command byte of Toggle01.
  
if (instance == 0x01) {

  if (status01_byte[6] == 0x00) {
    status01_byte[6] = 0x04;
    status01_byte[1] = 0xC8;
  } else {
    status01_byte[6] = 0x00;
    status01_byte[1] = 0x00;
  }
  
  CanMessage msg;
  msg.flags = canMSG_EXT;
  msg.id = 0x19FEDA01; //gives Priority=6, PGN=1FEDA, SA =01
  msg.dlc = 8;

  for(int value = 0; value < 8; value++) {
    msg.data[value] = status01_byte[value];  //This loop transfers the data from status_byte[x] to the message to be put on the bus.
  }
  canWrite(msg);
  return;
}
//only on CanMessage<0> (Toggle01)x { is open at this point
else {
if (instance == 0x02) {

  if (status02_byte[6] == 0x00) {
    status02_byte[6] = 0x04;
    status02_byte[1] = 0xC8;
  } else {
    status02_byte[6] = 0x00;
    status02_byte[1] = 0x00;
  }
  
  CanMessage msg;
  msg.flags = canMSG_EXT;
  msg.id = 0x19FEDA01; //gives Priority=6, PGN=1FEDA, SA =01
  msg.dlc = 8;

  for(int value = 0; value < 8; value++) {
    msg.data[value] = status02_byte[value];  //This loop transfers the data from status_byte[x] to the message to be put on the bus.
  }
  canWrite(msg);
  return;
}

else {
if (instance == 0x03) {

  if (status03_byte[6] == 0x00) {
    status03_byte[6] = 0x04;
    status03_byte[1] = 0xC8;
  } else {
    status03_byte[6] = 0x00;
    status03_byte[1] = 0x00;
  }
  
  CanMessage msg;
  msg.flags = canMSG_EXT;
  msg.id = 0x19FEDA01; //gives Priority=6, PGN=1FEDA, SA =01
  msg.dlc = 8;

  for(int value = 0; value < 8; value++) {
    msg.data[value] = status03_byte[value];  //This loop transfers the data from status_byte[x] to the message to be put on the bus.
  }
  canWrite(msg);
  return;
}

else {
if (instance == 0x11) {

  if (status11_byte[6] == 0x00) {
    status11_byte[6] = 0x04;
    status11_byte[1] = 0xC8;
  } else {
    status11_byte[6] = 0x00;
    status11_byte[1] = 0x00;
  }
  
  CanMessage msg;
  msg.flags = canMSG_EXT;
  msg.id = 0x19FEDA01; //gives Priority=6, PGN=1FEDA, SA =01
  msg.dlc = 8;

  for(int value = 0; value < 8; value++) {
    msg.data[value] = status11_byte[value];  //This loop transfers the data from status_byte[x] to the message to be put on the bus.
  }
  canWrite(msg);
  return;
}
}
}
}
}

on stop {
   canBusOff();
}