CAN Terminals

CAN is a protocol, very common in automotive systems. It is also being used in several spacecraft and by hobbyists. CAN is a multi-node bus, meaning that a bus model is needed for it. TEMU comes with a model of the OpenCores CAN controller (with the AMBA P&P extensions for GRLIB compatibility) and a simple bus model. With simple, it is meant that the bus model broadcasts every message and does not provide any built-in routing or filtering. Broadcasting is potentially expensive for large CAN networks, and for these cases it is possible to replace the simple CAN bus model with a custom one.

You need to implement three functions in a CAN device: connected, disconnected and receive. The temu_CanDevIface is defined in temu-c/Bus/Can.h. The CAN connected function is called when the device has been connected to a CAN bus, it needs to ensure that the bus interface reference is saved in the device model:

void
connected(void *obj, temu_CanBusIfaceRef bus)
{
  MyDevice *dev = (MyDevice *)obj;
  dev->bus = bus;
  const char *busName = temu_nameForObject(bus.Obj);
  temu_logInfo(obj, "connected to CAN bus %s", busName);
}

The disconnected function is called whenever the user disconnects a CAN device from the bus (e.g. to simulate someone/something severing the cable). This function should clear the CAN bus property:

void
disconnected(void *obj)
{
  MyDevice *dev = (MyDevice *)obj;
  // Stop any in-flight events
  // Disconnect the bus iface reference
  dev->bus.Iface = NULL;
  dev->bus.Obj = NULL;
  temu_logInfo(obj, "disconnected");
}

The third function is the receive function which is responsible for handling incoming frames, note that the device probably need to filter out messages:

void
receive(void *obj, temu_CanFrame *frame)
{
  MyDevice *dev = (MyDevice *)obj;
  int rtr = temu_canIsRemoteTransmissionRequest(frame);
  int len = frame->Length;
  temu_logInfo(obj, "received CAN frame RTR = %d, len = %d", rtr,
               (int)frame->Length);
  if (len > 8)
    len = 8;
  if (!rtr) {
    for (int i = 0; i < len; ++i) {
      temu_logInfo(obj, "\tFrame data %u", (unsigned)frame->Data[i]);
    }
  }
  temu_canSetAck(frame);
}

Finally, the interface struct should be constructed and it needs to be registered in the CAN device plugin initialisation function:

static temu_CanDevIface CanIface = {
    connected,
    disconnected,
    receive,
};

Plug-in init:

TEMU_PLUIGIN_INIT
{
  temu_Class *c = temu_registerClass("MyCANDevice", create, dispose);
  temu_addProperty(c, "bus", offsetof(MyDevice, bus), teTY_IfaceRef, NULL, NULL,
                   "CAN bus object");
  temu_addInterface(c, "can_a", TEMU_CAN_DEV_IFACE_TYPE, &CanIface, 0,
                    "CAN interface");
}

When running TEMU, there are two items that must be done in order to use the CAN bus and the new CAN device, the first is to create a CAN bus object (CAN is a multi-node bus and therefore needs a bus object for message routing). The SimpleCANBus class is available in the BusModels plugin:

temu> import BusModels
temu> object-create class=SimpleCANBus name=canbus0

Then instantiate your CAN device and connect it and the canoc0 controller (created by the ut700.temu-script) to the bus, this connection is done using the can-connect command:

temu> object-create class=MyCANDevice name=candev0
temu> can-connect bus=canbus0:CanBusIface dev=candev0:can_a
temu> can-connect bus=canbus0:CanBusIface
dev=canoc0:CanDevIface

The last thing is to have some embedded software that can be used to control the CAN OC controller:

struct CAN_OC_Device {
  uint8_t Ctrl;
  uint8_t Command;
  uint8_t Status;
  uint8_t Interrupt;
  uint8_t AcceptCode;
  uint8_t AcceptMask;
  uint8_t BusTiming[2];
  uint8_t unused0[2];
  uint8_t TxID[2];
  uint8_t TxData[8];
  uint8_t RxID[2];
  uint8_t RxData[8];
  uint8_t unused1[1];

  uint8_t ClockDivider;
};
rtems_task
Init(rtems_task_argument ignored)
{
  volatile struct CAN_OC_Device *CAN_OC
    = (struct CAN_OC_Device*)0xfff20000;
  printk("OBSW: Will send a CAN message\n");
  CAN_OC->Ctrl = 0; // Exit reset mode and disable Irqs
  CAN_OC->TxID[0] = 0xaa;
  CAN_OC->TxID[1] = 0xe1; // Length is 1
  CAN_OC->TxData[0] = 42; // Payload
  CAN_OC->Command = 1; // Transmission request
  sleep(1);
  printk("OBSW: Just sent the CAN message\n");
  exit( 0 );
}

Now run the emulator and see the logging done by the OBSW and the CAN device.