TEWS TIP810 Driver for EPICS
Version 2.9
Andrew Johnson <anj@aps.anl.gov>
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:
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.
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);
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
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.
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
iocsh> t810Create("CAN1", 0, 1, 0x60, 500) Value = 0
Shutdown routine, resets all devices to stop interrupts.
int t810Shutdown(void *dummy);
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.
void
Initialise driver and all registered hardware.
int t810Initialise(void);
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.
int
Symbol/Value Meaning 0 OK ENOMEM malloc() returned NULL (drvIpac) errors from ipmIntConnect()
Display a report giving the status of all TEWS TIP810 devices. This is registered as an iocsh command.
int t810Report(int interest);
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.
int
Symbol/Value Meaning 0 OK S_t810_badDevice Bad or corrupted internal device table found
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
Test routine, sends a single test message to the named CANbus.
int canTest (char *pbusName, canID_t identifier, int rtr, int length, char *data);
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.
int
Symbol/Value Meaning 0 OK -1 Error
iocsh> canTest "CAN1", 0x33, 0, 4, "STOP"
Return device pointer for given CAN bus name.
int canOpen(char *busName, canBusID_t *pbusID);
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.
int
Symbol/Value Meaning 0 OK S_can_noDevice No matching device name found.
canBusID_t can1; if (canOpen("CAN1", &can1)) { printf("Can't open CAN1\n"); return -1; }
Parse a CAN address string into a canIo_t structure
int canIoParse(char *canString, canIo_t *pcanIo);
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.
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
canIo_t myIo; int status; status = canIoParse("CAN1/20:0126.4 0xfff", &myIo) if (status) { printf("Address string rejected\n"); return -1; }
Writes a message to the given CANbus
int canWrite (canBusID_t busID, const canMessage_t *pmessage, int timeout);
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.
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
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); }
Register CAN message call-back
int canMessage(canBusID_t busID, canID_t identifier, canMsgCallback_t *pcallback, void *pprivate);
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.
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
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);
Delete CAN message call-back
int canMsgDelete(canBusID_t busID, canID_t identifier, canMsgCallback_t *pcallback, void *pprivate);
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.
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
To delete the call-back registered in the previous canMessage() example:
status = canMsgDelete(myIo.canBusID, myIo.identifier, myCallback, &value);
Register CAN error signal call-back
int canSignal(canBusID_t busID, canSigCallback_t *pcallback, void *pprivate);
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.
int
Symbol/Value Meaning 0 OK S_can_badDevice bad device identifier ENOMEM malloc() returned NULL
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"); }
Reset CAN chip and message and error counters. This is registered as an iocsh command.
int canBusReset(char *busName);
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().
int
Symbol/Value Meaning 0 OK S_can_noDevice No matching device name found.
iocsh> canBusReset "CAN1"
Stop CAN interface. This is registered as an iocsh command.
int canBusStop(char *busName);
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().
int
Symbol/Value Meaning 0 OK S_can_noDevice No matching device name found.
iocsh> canBusStop "CAN1"
Restart a stopped CAN interface. This is registered as an iocsh command.
int canBusReset(char *busName);
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.
int
Symbol/Value Meaning 0 OK S_can_noDevice No matching device name found.
iocsh> canBusRestart "CAN1"
Send Remote Transmission Request and wait for reply.
int canRead(canBusID_t busID, canMessage_t *pmessage, double timeout);
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.
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
int status; canMessage_t myBuffer; myBuffer.identifier = 139; myBuffer.length = 4; status = canRead(canID, &myBuffer, -1.0);