Dec 10

Based on the RoboCAN specification, I implemented the protocol on the AT90CAN and in Delphi using the CAN USB interface. For the AT90CAN it is placed in a second unit that makes use of the CAN.c unit I already wrote in order to process single CAN frames.

The logic behind the code is simple. It starts with a buffer of 1024 bytes called CANBuffer, this buffer is (only) used for storing the data of incoming and outgoing messages. The only clever part of this buffer is that a call can be made to ClaimBufferSpacewith a required number of bytes. This function will find the first consequatative block of bytes in the buffer that can hold these bytes and return a the position of the first bytes in the buffer. If message A takes 10 bytes, it will return BufferPos=0, message B takes 20 bytes, it will return BufferPos=10. If message is is completed and processed, this area could be used again. If a call comes in for message C that takes 12 bytes it will not fit since message B is not processed. As a result, BufferPos = 30 will be returned. The 1024 bytes is not based on any experience. Time will tell if this is too much or too less.

The next part is an array called MessageBuffer that holds 10 records of below data:

  • unsigned char FromAddress
  • unsigned char ToAddress
  • unsigned char Flags
  • unsigned char Command
  • unsigned char NumberOfBytes
  • unsigned char BytesProcessed
  • unsigned int   BufferPosition
  • unsigned char TimeStamp
  • unsigned char MessageID

The FromAddress and ToAddress hold the device address of the sender and the receiver. Flags is a byte that holds settings of the message and can be a combination of below bits:

  • mf_CommandFrame  0x01    b0, 0=acknowledgement frame, 1=command frame
  • mf_NormalFrame      0x02     b1, 0=interrupt frame, 1=normal frame
  • mf_Transmit             0x04     b2, 0=message buffer used for read, 1=for write
  • mf_Finished              0x08     b3, 0=message being processed, 1=finished processed
  • mf_Retry                   0x30     b5/b4, holds the number of retries during send
  • mf_Reserved             0x40     b6 is reserved, should be 0
  • mf_InUse                  0x80      b7, 0=message buffer is free, 1=busy

When a message is being send, the function FindFreeMessageBufferwill scan through the array and checks if the mf_InUse flag is 0. When sending a message, the search starts at record 0 and continuous upwards until a free one is found or when it exceeds number 7. Reason for this is that the record number is use as MessageID for sending the message, this will ensure that always an unique MessageID is used. When receiving the first frame of a message the search will start at record 9 and continuos downwards until a free record has been found. Also here the 10 records are not based on any experience. Time will tell if this is too much or too less.

When a message is offered for sending a free MessageBuffer is found and all the fields in the record will be set. A call is made to ClaimBufferSpace to claim storage capacity based on NumberOfBytesand the returned BufferPos is stored in the record after which all the data to be send is copied into the CANBuffer.

The actual sending of the seperate frames is taken care of by the funtion SendRoboCANFrame. When this function is called it scans through the used records in MessageBuffer  to find the message with the highest priority. When found, it determines how much packages are already send using the BytesProcessed field of the record and it configures a single CAN frame that will be offered to the CAN interface for sending. After this the BytesProcessed field is updated and the application can continue execution. When the byte is send the CAN interface triggers an interrupt and the Interrupt Service Routine will call SendRoboCANFrameagain repeating the process of find the highest priority waiting message. If no messages are waiting this process will stop automaticly since no interrupts will be triggered anymore. For that reason the function SendRoboCANFrame must be called ones by the function that submits a message for sending.

Receiving messages works in a simular manor except that there are 4 MOB’s setup in parallel all filtering for the same priority. This is to prevent frames are lost when more devices decide to communicate in parallel with the device. The number 4 is again not based on any experience.

Any frame that is addressed to the device is execepted in any of the 4 MOB’s. When a frame is received an interrupt is triggered and the frame is read. A function RoboCANFrameReceivedis called that checks if this is a frame using PackageNumber 0. If so, a free MessageBuffer  is found, filled in and memory is claimed using ClaimBufferSpace. Possible data is directly stored in the CANBuffer and the fields BytesProcessed is updated.

If a PackageNumber is used higher than 0 the previously used MessageBuffer  is found using the FromAddress field and the MessageID. Again the data is stored after which a check is done if the fields BytesProcessed  and NumberOfBytesare the same. In such a case the full message is received and an Acknowledgement message is send usign the same function for sending a message as the application will do: SendRoboCANMessage. This will stored the message using a MessageBuffer and store the data in the CANBuffer, the interrupt based SendRoboCANFrame will than take over based on the previous explained algorithm of finding the highest priority.

Since a message was fully received a callback function ProcessRoboCANReceived that holds a structure TRoboCANDataas parameter. The definition of the function is part of the RoboCAN unit, but the actual implementation needs to be included in the host application making use of the RoboCAN protocol. The passed variable RoboCANData holds all the data of the message:

struct TRoboCANData
{
  unsigned char Interrupt;
  unsigned char Acknowledge;
  unsigned char ToAddress;
  unsigned char FromAddress;
  unsigned char Command;
  unsigned char NumberOfBytes;
  unsigned char *Data;                 
};

The function ProcessRoboCANReceived is part of the Interrupt Service Routine that received the last CAN frame. By using such a callback function there is one point in the application were all commands come in at the moment they arrived. The downside is that all other CAN processing is on hold until the function completes so processing of the command may not take much time. Time will tell if this is a smart method, it can easily be converted to a non-interrupt version. When the function is completed, the MessageBuffer is freed and the data stored in the CANBuffer is lost.

The same process is used for reporting that a message has been send and was acknowledged by the receiver. At that time a callback function ProcessRoboCANSend is called as part of the ISR that received the acknowledge frame. Also here the original message is passed as a variable so that any post processing can take place or application status can be updated. When the function completes also here the MessageBuffer is freed and the data stored in the CANBuffer is lost.

During transmission things can go wrong. If so, the RoboCAN protocol will retry maximum 4 times (if possible) before finally reporting the error. For this a timer is used (TIMER2) that is set to trigger an interrupt every 25ms. Every 25ms the TimeStamp field is increased by one for all MessageBuffer s that are in use. If at that time it reaches the value 10 (so 25ms in total) the two mf_Retry bits in the Flags field are tested to see if the number of retries exceeded 4 time. If so, the message is canceled and an error call back function is executed. Whenever a message that is in progress has been modified from (for example) receiving or sending a single CAN frame, the TimeStamp field is set to 0 to show progress has been made.

There are three error callback functions, ProcessRoboCANSendError for reporting send errors, ProcessRoboCANSendError for receiving errors and ProcessRoboCANBusError for interface errors. Main reason for three different functions is that the parameters are different. On a send error the actual message is passed back next to the error. On receive errors the message has not been received, so only the device address that was trying to send a message is passed next to the error. On interface errors there is no message or device known, so only the error is passed.

The RoboCAN units for the AT90CAN can be downloaded here and a small application that shows how to work with the callback functions. It will be replaced by every new version, so you always have the latest one. For the PC a protocol test program has been developed in Delphi 2007 to test the protocol on how it handles errors. You can use it if you like, next to sending and receiving RoboCAN based messages it can also send and receive single CAN frames (of course). The RoboCAN framework will be re-used for writing a component so RoboCAN can easily be integrated in any Delphi based PC application.

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.