drvTip810 - CAN Bus Driver

TEWS TIP810 Driver for EPICS
Version 2.9

Andrew Johnson <anj@aps.anl.gov>

Contents


1. Introduction

This document describes the software interface to the TEWS TIP810 Industry-Pack Module for EPICS. This software is an IPAC Module Driver and uses the services of the drvIpac Industry Pack Driver to interface to the IPAC Carrier Board - see the accompanying document drvIpac - Industry Pack Driver for details of this driver software. The device support routines provided for use with EPICS are described in the document devCan - CAN Bus Device Support which briefly covers the use of the various record types supported.

The following routines provided by this driver are described in detail below:


2. TIP810 Driver Usage

The software provides an interface to higher level software which, other than device initialisation, is not specific to the TIP810 but could be used with a CANbus driver for a different hardware interface. The interface to the higher level software is provided in two header files: canBus.h contains all of the generic CANbus definitions and declarations, while drvTip810.h incorporates the additional declarations and definitions which are specific to the TIP810 module. The routines which are specific to the TIP810 or meant for use from the vxWorks shell are described individually in this section, while the generic CANbus interface routines are described in section 3 below.

The TEWS TIP810 IP module contains a Philips pca82c200 stand-alone CAN-controller chip which performs all of the CANbus interfacing functions. The interface to this chip is defined in a separate header file pca82c200.h which declares the register interface structure and the bit-patterns for the various on-chip registers.

The CAN-controller chip does not contain the necessary interrupt vector logic required by the IndustryPack interface however, so the TIP810 module implements this in additional logic, including a register to hold the vector number to be used.


t810Create()

Registers a new TIP810 device with the driver. In EPICS, this is registered as an iocsh command.

int t810Create (char *pbusName, int card, int slot,
                int irqNum, int busRate);

Parameters

char *pbusName
String which comprises a unique identifier for this particular CAN Bus. This must be a static string which should not be changed while the driver is loaded and running (vxWorks and EPICS IOC shell string literals have this property). While t810Create() enforces no restrictions on the characters which may be used in the name, if the canIoParse() routine is used the name should contain alphanumeric characters only.
int card, int slot
Ipac Driver carrier and slot numbers which identify the IPAC module, for use with drvIpac.
int irqNum
Interrupt vector number allocated for this device.
int busRate
CAN bus rate in Kbits/sec. This parameter can have one of the values given in the following table. Other rates will require modifications to the driver source code. The Kvaser standard uses different bit timings which are not compatible with those used by Tews and so require special bit-rate entries.
busRate CANbus bit rate
5 5 Kbits/sec
10 10 Kbits/sec
20 20 Kbits/sec
50 50 Kbits/sec
100 100 Kbits/sec
125 125 Kbits/sec
250 250 Kbits/sec
500 500 Kbits/sec
1000 1000 Kbits/sec
1600 1600 Kbits/sec
-125 Kvaser 125 Kbits/sec
-250 Kvaser 250 Kbits/sec
-500 Kvaser 500 Kbits/sec
-1000 Kvaser 1000 Kbits/sec

Description

This routine will usually be called from the IOC start-up script. It is used to inform the driver about each TIP810 module which it is to control, providing information on how to find the module (the IPAC carrier and slot numbers) and what CANbus bit rate is to be used. Each module is given a name which is matched during calls to the canOpen() routine to identify the particular module again.

The code checks that the given name and card/slot numbers are unique and point to a real Tews TIP810 module, then it creates a new device table and initialises it and some of the chip registers. At this stage the device is not activated but held in the reset state.

Returns

int
Symbol/Value Meaning
0 OK
S_t810_badBusRate Bus Rate not supported
S_t810_duplicateDevice another TIP810 already using given name and/or IPAC address
(drvIpac) errors returned by ipmValidate()
ENOMEM malloc() returned NULL

Example

iocsh> t810Create("CAN1", 0, 1, 0x60, 500)
Value = 0

t810Shutdown()

Shutdown routine, resets all devices to stop interrupts.

int t810Shutdown(void *dummy);

Parameters

void *dummy
This parameter is not used by the routine but is required to allow it to be used as an epicsAtExit() routine.

Description

When t810Initialise() is called, this routine will be registered as an epicsAtExit() callback. It resets the CAN controller chips on all known TIP810 modules when called. It can also be run by applications programs to turn off all the module drivers in the event of some catastrophic failure, but it may be necessary to reinitialise the drivers to re-enable them afterwards.

Returns

void

t810Initialise()

Initialise driver and all registered hardware.

int t810Initialise(void);

Parameters

void
None.

Description

This routine is called during iocInit(), which must be placed after all t810Create() calls in the start-up script. It creates a message queue to hold received messages and starts a task named canRecvTask to distribute them to the routines that have asked to be informed about them. Finally it completes the initialisation of the CAN controller chip and interrupt vector registers for all known TIP810 devices and starts them running.

Returns

int
Symbol/Value Meaning
0 OK
ENOMEM malloc() returned NULL
(drvIpac) errors from ipmIntConnect()

t810Report()

Display a report giving the status of all TEWS TIP810 devices. This is registered as an iocsh command.

int t810Report(int interest);

Parameters

int interest
Interest level, indicating how much detail is required and what information to show.

Description

Outputs (to stdout) a list of all the TIP810 devices created, their IP carrier & slot numbers and the bus name string. For interest=1 it adds message and error statistics; for interest=2 it lists all CAN IDs for which a call-back has been registered; for interest=3 the status of the CAN controller chip is given.

Returns

int
Symbol/Value Meaning
0 OK
S_t810_badDevice Bad or corrupted internal device table found

Examples

iocsh> t810Report(1)
TEWS tip810 CANbus Ip Modules
  'CAN1' : IP Carrier 0 Slot 1, bus rate 500 Kbits/sec
        Messages Sent       :    75
        Messages Received   :    43
        Message Overruns    :     0
        Discarded Messages  :     4
        Last Discarded ID   : 0x206
        Error Interrupts    :     0
        Bus Off Events      :     0
-> t810Report(2)
TEWS tip810 CANbus Ip Modules
  'CAN1' : IP Carrier 0 Slot 1, bus rate 500 Kbits/sec
        Callbacks registered: 
            0x1c 0x1d 0x1e 0x1f 0x200 0x202 0x204
        canRead Status : Idle
-> t810Report(3)
TEWS tip810 CANbus Ip Modules
  'CAN1' : IP Carrier 0 Slot 1, bus rate 500 Kbits/sec
    pca82c200 Chip Status: 
        Bus Status             : Bus-On
        Error Status           : Ok
        Data Overrun           : Ok
        Receive Status         : Idle
        Receive Buffer Status  : Empty
        Transmit Status        : Idle
        Transmission Complete  : Complete
        Transmit Buffer Access : Released

canTest()

Test routine, sends a single test message to the named CANbus.

int canTest (char *pbusName, canID_t identifier, int rtr,
             int length, char *data);

Parameters

char *pbusName
Device name identifying the particular TIP810 device to use.
canID_t identifier
The CANbus message identifier to send.
int rtr
0 for a normal CAN message, non-zero to send a Remote Transmission Request packet.
int length
Number of bytes in the data field for this CANbus message type.
char *data
Pointer to the data to be sent. Ignored for RTR packets.

Description

This routine is provided as a diagnostic, to allow the system developer to generate CANbus messages and RTR packets from the vxWorks shell. It should not be used from within an application. It makes use of the canOpen() and canWrite() routines, and responds to errors reported by those routines by printing a message and returning -1.

Returns

int
Symbol/Value Meaning
0 OK
-1 Error

Example

iocsh> canTest "CAN1", 0x33, 0, 4, "STOP"

3. Routines for CANbus Applications

canOpen()

Return device pointer for given CAN bus name.

int canOpen(char *busName, canBusID_t *pbusID);

Parameters

char *busName
Device name that identifies the particular TIP810 device to use.
canBusID_t *pbusID
Pointer to a canBusID_t to hold the device identifier.

Description

Searches through the list of registered TIP810 devices for one which matches the name given, and returns a device identifier for it. This identifier is a required parameter for all of the remaining can driver routines. String searching in this manner is not particularly fast if several devices have been registered so this routine is intended to be used mainly when an application starts up. It may be used as often as desired however - there is no associated canClose() routine.

Returns

int
Symbol/Value Meaning
0 OK
S_can_noDevice No matching device name found.

Example

canBusID_t can1;
if (canOpen("CAN1", &can1)) {
    printf("Can't open CAN1\n");
    return -1;
}

canIoParse()

Parse a CAN address string into a canIo_t structure

int canIoParse(char *canString, canIo_t *pcanIo);

Parameters

const char *canString
Address string to be converted.
canIo_t *pcanIo
Pointer to a structure which will be used to return the converted address information.

Description

canIoParse() is used by the EPICS device support routines to convert record hardware addresses, but can be used by any application with similar requirements. It is intended to provide a standard way of converting the parameters which are needed to specify a portion of a particular CANbus message type from an ASCII string to their binary representation as a structure. The canIo_t structure is defined as a typedef in the canBus.h header as follows:

typedef struct {
    char *busName;
    double timeout;
    canID_t identifier;
    epicsUInt16 offset;
    epicsInt32 parameter;
    char *paramStr;
    canBusID_t canBusID;
} canIo_t;

The address string passed to this routine consists of a series of elements, some of which are optional.

The first element is the bus name, which should consist of alphanumeric characters only. The name is terminated immediately before the first "/" or ":" character in the string, and after omitting any leading white-space the characters forming the bus name are copied to a newly allocated buffer, the address of which is placed in pcanIo->busName.

An oblique stroke ("/") after the bus name introduces an optional timeout element, which is an integer number of milli-seconds to wait for a response for this particular type of message. This is converted into seconds as a double and placed in pcanIo->timeout. If no timeout element is included, the timeout is set to -1.0 which means wait forever.

The CANbus message identifier is preceded by a colon (":"), and must result in one of the legal CANbus identifiers in the range 0 through 2047 (with holes). The identifier itself can be specified as a single number, or in several parts separated by plus signs, which are all summed. The numbers here can be given in any of the standard 'C' formats as converted by strtol(), so negative, hex or octal numbers may be used as desired.

If the identifier is followed by a decimal point ("."), the following element is an optional byte offset into the CANbus message. The offset is stored as an unsigned 16-bit integer (using strtoul() again for the conversion), but to remain within the limits of the message buffer it should be restricted to a maximum value of seven. The converted value is placed in pcanIo->offset, which defaults to zero if no offset is given.

The final element is a general-purpose parameter, introduced by a space or tab character. The value is first converted to a signed 32-bit integer using strtol() which is placed in pcanIo->parameter. A pointer to any remaining characters is placed in pcanIo->paramStr.

If the string is successfully converted without errors, canIoParse will also call canOpen() to initialise the pcanIo->canBusID bus identifier.

Returns

int
Symbol/Value Meaning
0 OK
S_can_badAddress illegal input string or NULL input parameters
S_can_noDevice No matching device name found
ENOMEM malloc() returned NULL

Example

canIo_t myIo;
int status;
status = canIoParse("CAN1/20:0126.4 0xfff", &myIo) 
if (status) {
    printf("Address string rejected\n");
    return -1;
}

canWrite()

Writes a message to the given CANbus

int canWrite (canBusID_t busID, const canMessage_t *pmessage, int timeout);

Parameters

canBusID_t busID
CANbus device identifier, obtained from canOpen()
const canMessage_t *pmessage
The message to be transmitted.
double timeout
Delay in seconds, indicating how long to wait for exclusive access to the TIP810 module. A negative delay means want forever.

Description

This routine is called to transmit a message on a particular CANbus. The canMessage_t type is defined as the following structure in canBus.h:

typedef struct {
    canID_t identifier;              /* 0 .. 2047 with holes! */
    enum {
        SEND = 0, RTR = 1
    } rtr;                           /* Remote Transmission Request */
    epicsUInt8 length;               /* 0 .. 8 */
    epicsUInt8 data[CAN_DATA_SIZE];  /* CAN_DATA_SIZE = 8 */
} canMessage_t;

When called, canWrite() obtains exclusive access to the TIP810 transmission buffer, converts the message into the correct form for the interface chip and copies it to the hardware registers. Finally it sends a Transmit Message command to the chip. The exclusive access semaphore will be released by the Interrupt Service Routine when it receives a notification from the chip that the message has been transmitted successfully.

Returns

int
Symbol/Value Meaning
0 OK
S_t810_badDevice canBusID not valid
S_can_badMessage invalid field in the message buffer
S_t810_transmitterBusy system fault somewhere
S_t810_timeout transmit semaphore timed out

Example

canIo_t myIo;
canMessage_t message;
char data[] = "STOP";
int status;
status = canIoParse("CAN1/20:0126 0xfff", &myIo); 
if (status == OK) {
    message.identifier = myIo.identifier;
    message.rtr        = SEND;
    message.length     = strlen(data);
    memcpy(&message.data[0], data, message.length);
    status = canWrite(myIo.canBusID, &message, myIo.timeout); 
}

canMessage()

Register CAN message call-back

int canMessage(canBusID_t busID, canID_t identifier,
               canMsgCallback_t *pcallback, void *pprivate);

Parameters

canBusID_t busID
CANbus device identifier, obtained from canOpen()
canID_t identifier
The CAN message identifier for which call-backs are required.
canMsgCallback_t *pcallback
The address of the routine to be called whenever matching messages are received.
void *pprivate
A parameter which is passed to the call-back routine to help it identify its context.

Description

This routine is used to add a call-back routine for a particular CAN message identifier on the given CANbus. Call-backs can be registered for any CAN message identifier, and there can be more than one call-back using the same ID - all routines are called in turn when a message with the relevant identifier is received. The call-back routine must not change the message at all, and should copy any information it needs from the message buffer before returning. Processing should still be kept to a minimum though as the callback is executed in a single high priority thread which services all TIP810 devices. The call-back routine's prototype is defined as a canMsgCallback_t:

void callback(void *pprivate, const canMessage_t *pmessage);

The pprivate value supplied when the call-back is registered with canMessage() will be passed to the call-back routine with each message to allow it to identify its context.

Returns

int
Symbol/Value Meaning
0 OK
S_can_badMessage bad identifier or NULL call-back routine
S_can_badDevice bad device pointer
ENOMEM malloc() returned NULL

Example

void myCallback(void *ctx, const canMessage_t *pmessage) {
    /* Update value whenever message arrives */
    short *pvalue = ctx;
    memcpy(pvalue, &pmessage->data[0], sizeof(short)); 
}

...

int status;
static short value;
status = canMessage(myIo.canBusID, myIo.identifier, myCallback, &value);

canMsgDelete()

Delete CAN message call-back

int canMsgDelete(canBusID_t busID, canID_t identifier,
               canMsgCallback_t *pcallback, void *pprivate);

Parameters

canBusID_t busID
CANbus device identifier, obtained from canOpen()
canID_t identifier
The CAN message identifier for which call-backs were registered.
canMsgCallback_t *pcallback
The address of the routine being called whenever matching messages were received.
void *pprivate
The parameter passed to the call-back routine to help it identify its context.

Description

This routine is used to remove a call-back routine already registerd for a particular CAN message identifier on the given CANbus.

Exactly the same parameters given when the call-back was registered with canMessage() must be passed to canMsgDelete() for it to be successfully deleted.

Returns

int
Symbol/Value Meaning
0 OK
S_can_badMessage bad identifier or NULL call-back routine
S_can_noMessage no matching call-back routine
S_can_badDevice bad device pointer

Example

To delete the call-back registered in the previous canMessage() example:

status = canMsgDelete(myIo.canBusID, myIo.identifier, myCallback, &value);

canSignal()

Register CAN error signal call-back

int canSignal(canBusID_t busID, canSigCallback_t *pcallback,
              void *pprivate);

Parameters

canBusID_t busID
CANbus device identifier, obtained from canOpen()
canSigCallback_t *pcallback
The address of the routine to be called whenever there is a change in the bus status.
void *pprivate
A parameter which is passed to the call-back routine to help it identify its context.

Description

This routine is used to add a new call-back routine for CANbus error reports from the given CANbus. There can be any number of error call-backs on each device, and all are called in turn when the controller chip reports a Bus Error or Bus Off event. On systems that support interrupts, the call-back routine is executed in Interrupt Context, thus there may be restrictions in which OS and/or EPICS OSI routines can be used. In any case, processing within the callback routine should be kept to an absolute minimum. The call-back routine's protocype is of type canSigCallback_t:

void callback(void *pprivate, int status);

The pprivate value supplied to canSignal is passed to the call-back routine with the error status to allow it to identify its context. Status values will be one of

these being pre-processor macros defined in the header file. If the chip goes to the Bus Off state, the driver will attempt to restart it, thus a Bus Ok signal should follow almost immediately.

Returns

int
Symbol/Value Meaning
0 OK
S_can_badDevice bad device identifier
ENOMEM malloc() returned NULL

Example

void mySignal(void *pprivate, int status) {
    if (status == CAN_BUS_ERROR)
        epicsInterruptContextMessage(pprivate);
}

...

int status;
status = canSignal(myIo.canBusID, mySignal, "CAN Bus Error");
if (status) {
    printf("Couldn't register CAN signal handler.\n"); 
}

canBusReset()

Reset CAN chip and message and error counters. This is registered as an iocsh command.

int canBusReset(char *busName);

Parameters

char *busName
Device name to identify the particular TIP810 device to be reset.

Description

Resets the pca82c200 chip identified by the given busName, and clears all the counters associated with this device. This may clear some bus-related errors. All registered callbacks will remain active as before, although the chip may miss some incoming messages while resetting. This routine may also be used to restart an interface after a call to canBusStop().

Returns

int
Symbol/Value Meaning
0 OK
S_can_noDevice No matching device name found.

Example

iocsh> canBusReset "CAN1"

canBusStop()

Stop CAN interface. This is registered as an iocsh command.

int canBusStop(char *busName);

Parameters

char *busName
Device name identifing the particular TIP810 module to be stopped.

Description

Holds the pca82c200 chip identified by the given busName in the reset state, which prevents it from sending or receiving messages, or from sending message acknowledgements to other nodes. The interface can be reactiviated using either canBusRestart() or canBusReset().

Returns

int
Symbol/Value Meaning
0 OK
S_can_noDevice No matching device name found.

Example

iocsh> canBusStop "CAN1"

canBusRestart()

Restart a stopped CAN interface. This is registered as an iocsh command.

int canBusReset(char *busName);

Parameters

char *busName
Device name identifying the TIP810 device to be restarted.

Description

Restarts the pca82c200 chip identified by the given busName after it has been stopped by a call to canBusStop(). All registered callbacks will remain active as before and the message and error counters will continue to increment from their previous values.

Returns

int
Symbol/Value Meaning
0 OK
S_can_noDevice No matching device name found.

Example

iocsh> canBusRestart "CAN1"

canRead()

Send Remote Transmission Request and wait for reply.

int canRead(canBusID_t busID, canMessage_t *pmessage, double timeout);

Parameters

canBusID_t busID
CANbus device identifier, obtained from canOpen()
canMessage_t *pmessage
Message buffer to be used, with identifier and length fields set.
double timeout
Delay in seconds, indicating how long to wait for a response, including the delay in canWrite() to obtain exclusive access to the transmit buffer. This value is not an absolute delay but is used internally for two separate sequential semaphore time-outs, thus there could be delays totalling up to twice this period but the routine could still succeed. A negative delay means wait forever.

Description

Sends a CANbus Remote Transmission Request and waits for a reply on the same message identifier, returning the message received in the given buffer. On entry the message buffer must be initialised with the CANbus message identifier and length of the expected reply in the relevant fields.

The canRead() routine can be used along with canWrite() to create simple CANbus interface applications without the need to use the message call-back system. This will work in situations where the vxWorks system is the only device on the CANbus which initiates message traffic and there are no long delays in responses to RTRs. More complex applications which need to receive unsolicited messages will need to use the canMessage() call-back functions; these can be used at the same time as canRead(). Although the routine is safe to use in multi-tasking situations, the action of sending an RTR and waiting for a returned message will only be performed for one task at a time on each bus.

Returns

int
Symbol/Value Meaning
0 OK
S_t810_badDevice bad bus ID
S_can_badMessage bad message Identifier or length
S_t810_timeout timeout waiting for response

Example

int status;
canMessage_t myBuffer;
myBuffer.identifier = 139; 
myBuffer.length     = 4;
status = canRead(canID, &myBuffer, -1.0);

Andrew Johnson <anj@aps.anl.gov>