Recent releases of MegaSquirt-II™ have have Controller Area Network (CANbus) serial bus capability directly on the MegaSquirt-II™ daughter card. The CANbus capability allows MegaSquirt-II™ to "talk" to other CANbus enabled devices, such as the GPIO board. The MegaSquirt-II™ implementation of CANbus is a 'push/pull' system that allows a processor to request a value from the specific memory location on another processor, or to write a value to a memory location on another processor. It also has the ability to initiate a certain routine (can_xsub01()) on another processor.
It is important to note that the values are set based on the memory locations, not the variable names, and that the memory locations are specified by CAN ID, table number (aka,. "block"), and offset within the table. This means that the originating processor must 'know' these locations on the other processor (i.e., the programmer has to set them).
Controller Area Network (CANbus) features:
CANbus is a serial bus system with multi-master capabilities (meaning all CANbus nodes are able to transmit data and several CANbus nodes can request the bus simultaneously). The CAN bus system with real-time capabilities is the subject of the ISO 11898 international standard and covers the lowest two layers of the ISO/OSI reference model. In CANbus networks there is no addressing of subscribers or stations in the conventional sense, but instead, prioritized messages are transmitted. A transmitter sends a message to all CANbus nodes (broadcasting). Each node decides on the basis of the identifier received whether it should process the message or not. The identifier also determines the priority that the message enjoys in competition for bus access. The relative simplicity of the CANbus protocol means that very effort is needed to understand an implement it, and the CANbus chips interfaces make applications programming relatively simple.
The CANbus protocol is an ISO standard (ISO 11898) for serial data communication. The protocol was developed aiming at automotive applications. Today CANbus has gained widespread use and is used in industrial automation as well as in automotive applications.
The CANbus is a broadcast type of bus. This means that all nodes can "hear" all transmissions. There is no way to send a message to just a specific node; all nodes will invariably pick up all traffic. The CANbus hardware, however, provides local filtering so that each node may react only on the interesting messages.
As far as documentation on CANbus, the best documentation is the MSCAN application note for the HCS12 (or HC08). It discusses the entire protocol and then discusses the particular registers for specific processors. It is very detailed, and a 'must read' reference for those hoping to do CANbus programming. The other CANbus source is the Bosch specification. For people who want to dedicate their lives to CANbus, there is a CAN Open industrial consortium - but you have to pay to get any documents. It is also very technical. You would have to wade through days of documentation just to send a 1 or 0 across a wire a few times a day, so we recommend against using it.
MegaSquirt® CANbus Format
The MegaSquirt® EFI Controller's implementation of CANbus allows up to 14 auxiliary boards to be attached in a network, with each board having a unique address from 1 to 14. Address 0 is reserved for the MegaSquirt-II™ Controller and address 15 is for a message which is accepted by all boards. For each board there is a board type - a one byte number (1-255) that specifies the type of board. For example, type 1 is reserved for the MegaSquirt-II™ main controller, 2 for a router board, type 3 for a GPIO board, and the rest are wide open. (0 means you currently have no board in that slot).
Inside the MegaSquirt-II™ code there are routines for sending/receiving data using a consistent header message that specifies:
The 29 bit CANbus message header is divided into blocks across four 8-bit registers (IDR0 → IDR3) that mean different things. The details are:
Register Bits → | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Header Bits | |
CAN_RB_IDR0 (0x20) | var_offset | 28 ← 21 | ||||||||
CAN_RB_IDR1 (0x21) | var_offset | SRR=1 | IDE=1 | msg_type | 20 ← 15 | |||||
CAN_RB_IDR2 (0x22) | From ID | To ID | 14 ← 7 | |||||||
CAN_RB_IDR3 (0x23) | var_blk | spare | RTR | 6 ← 0, RTR |
These blocks specify:
When sending, the data itself goes into cx_datbuf[i][j] ([i] is the message number, [j] is the jth character of 8 in the data buffer), and the size of the data goes into cx_varbyt[].
For MSG_CMD messages, the variable is put in the destination table and offset specified in the message itself, i.e., in the appropriate variable. For MSG_REQ messages, we are requesting the value at a specific table and offset in the 'other' ECU, and again the message contains the table and offset. However, how do we know where to put it in our memory structure when the MSG_RSP comes back in reply to our MSG_REQ? The answer is that we store these as can[].cx_myvarblk[], can[].cx_myvaroff[] and can[].cx_varbyt[] on the requesting ECU (these go into CAN_TB0_DLR, CAN_TB0_DSR0, & CAN_TB0_DSR1 + CAN_TB0_DSR2 respectively). So the requested variable is at destvarblk on the other processor, but myvarblk on the originating processor.
There can be more than one message pending from any given processor (up to NO_CANMSG=10, in fact), so the variables above are given the prefix can[x]., where x specifies the message to which the value belongs. Note that can[0]. is to hold Rx, TxISR messages, can[1]., can[2]., ... ,can[10]. are for main loop messages (so they don't get clobbered by ISR). And:
These are defined in a structure called "canmsg { }" in the source code.
For example, the message might be "This is processor X, asking processor Y to send me the data in block x, offset y" or "I am processor X, sending processor Y the following data to put in block x, offset y").
This CANbus implementation has a lot of flexibility, so someone who wanted a lot of data could insert 5 GPIO boards. The combinations can change day to day - just update the board_id_type table which will be in MegaTune so the MegaSquirt-II™ knows what is connected.
For example, to turn on a fan based on coolant temperature using a GPIO board, the GPIO processor can periodically poll MegaSquirt-II for the coolant temperature and turn the fan on accordingly.
The CANbus message identifier contains the board address. Only one set in stone right now is the MegaSquirt® Controller is address 0, other board types need to be allocated.
Beyond this there is no standard set up. For instance, a GPIO board could be type 1.
For testing purposes, the current MegaSquirt® code has a section which sends a CANbus packet containing the outpc.rpm from one MegaSquirt-II™ processor to another periodically (when enabled via an #ifdef statement) - here is the code for loading the ring buffer:
#ifdef CAN_TEST // sample code to send periodic messages if(lmms > cansendclk) { cansendclk = lmms + 7812; // 1 sec(7812 x .128 ms) clk for(ix = 0;ix < 6;ix++) { // load ring buffer - send rpm (void)memcpy(&can[1].cx_datbuf[can[1].cxno_in][0],(char *)&outpc.rpm, 2); can[1].cx_msg_type[can[1].cxno_in] = MSG_CMD; can[1].cx_destvarblk[can[1].cxno_in] = 1; // Told to put rpm in outpc.spare[3]; below is offset can[1].cx_varoff[can[1].cxno_in] = (unsigned short)(&outpc.spare[3]) - (unsigned short)(&outpc); can[1].cx_dest[can[1].cxno_in] = 1; // send to device 1 can[1].cx_varbyt[can[1].cxno_in] = 2; // 2 bytes // This is where (in xmt ring buffer) to put next message if(can[1].cxno_in < (NO_CANMSG - 1)) can[1].cxno_in++; else can[1].cxno_in = 0; // increment counter if(can[1].cxno < NO_CANMSG) can[1].cxno++; else can[1].cxno = NO_CANMSG; } if(!(CANTIER & 0x07)) { // Following will cause entry to TxIsr without sending msg // since when CANTIER = 0, CANTFLG left as buff empty(>0). // If CANTIER has at least 1 int buf enabled, will enter // TxIsr automatically. CANTBSEL = CANTFLG; CANTIER = CANTBSEL; } } #endif // end sample CAN send code |
All you need to do is connect two MS-II™ boards together (or MS-II and GPIO) and enable this code. Be sure to set one of the MS-II™ processor's mycan_id to device 1 (the other should be 0). The wiring is:
There are termination resistors already active on the MS-II board.
On MS-II controllers, the CANbus paths are:
With the test code above, we have seen a reliable 600 CANbus packets a second. This is about 1.6 milliseconds per packet. And there are 8 data bytes per packet that are delivered that are user data. This was a point-to-point test with two devices.
However, if you were to load up the bus with a lot of modules and there can be latency issues.
One thing that helps is the message identifier on the CAN bus with a built-in priority mechanism. The value of the arbitration field dictates which message is sent. The bus is 'logic-low dominant', meaning that logic low is when the CANH and CANL signals are active, or driven from their differential recessive state (i.e. CANH is higher than CANL during dominant, and are the same voltage during recessive).
The CANbus is a 'non-destructive', 'bit-wise arbitration' bus. When two or more devices attempt to transmit simultaneously on the bus, the one device with the lowest arbitration number wins. So if one device sends out 0010 and another one sends out 0011 at the same time, the lower one (the 0010) will win always. Assuming the devices are all synced to the bus, when they (the multiple devices) start transmitting the bits are sent one at a time. A logic one does nothing to the bus and a logic zero pulls the CANH and CANL to their dominant states. As each device transmits each bit it then turns around and reads back the bit on the bus, and if what gets sent out matches what is read back then it proceeds with the next bit. If one device sends out a logic 1 and the readback is a logic zero then this means a second device is also attempting to transmit as well (and wins). In this case the first device will stop transmitting until the bus is clear.
Here is a time graph illustrating this (note that this is for a 11-bit message identifier, MegaSquirt-II™ uses the extended frame format of 29 bits):
Participant 2 gets knocked out at identifier step 5 because it is logic one while participants 1 and 3 are logic low - participant 2 stays logic high for the rest of the message because it has lost.
Participant 1 gets knocked out at identifier step 2, its high when participant 3 is low. Participant 3 continues with the message to the end.
CANbus Display Variable Messages
Purpose
In anticipation of several simultaneous GPIO applications requesting messages to deliver the contents from the MS-II outpc structure, a way is needed to handle this in an efficient manner so the requests do not slow down the processor. There are 112 bytes of various 8 and 16 bit variables, and one 32 bit variable (time) in the outpc. structure. This means that 14 separate 8-bytes messages, plus a request message, are required to get all the outpc. data.
But it would be rare that more than 5-10 variables would be required by any one application, but it is also likely that these would not be exactly the same variables for different applications. Also, even if only 2 variables are required, if they were not consecutively located in outpc, this would at present require 2 request and 2 response messages.
The following is a specification for handling the outpc messages in a flexible and much more efficient manner.
Message Structure
The following message structure will be used:
#define MAX_BYTES_OUTMSG 24 // enough for 3 8-byte messages that should cover all // needs; if more needed, use a second or third message. #define MAX_ OUTMSGS 20 // enough for 6 GPIO devices to specify 3 messages each. typedef struct { unsigned short xrate; // =0 means the message is only transmitted when requested, // otherwise it indicates the message will be transmitted automatically // every xrate ms after receiving the first request. [Not currently used] unsigned char no_bytes; // The total number of bytes of all the variables in the message. // Must be < 112 = size of outpc. unsigned char offset[MAX_BYTES_OUTMSG]; // This holds the byte offsets of each of the // bytes in the message relative to start of outpc. For a short, // specify 2 consecutive offsets in the proper order for the requesting // processor. } can_outmsg;
This structure is instantiated (i.e. a real object in memory is created from the typedef template) as an array of structures in flash only (RAM is no longer used for this). An example is:
const can_outmsg outmsg[MAX_OUTMSGS] EEPROM_ATTR = { { 0, // xrate: =0 means the message is only transmitted when requested, // otherwise it indicates the message will be transmitted automatically // every xrate ms after receiving the first request. [Not currently used] 10, // no_bytes: the total number of bytes of all the variables in the message. // Must be < 112 = size of outpc. // // offset[MAX_BYTES_OUTMSG]: this holds the byte offsets of each of the // bytes in the message relative to start of outpc. For a short, specify // 2 consecutive offsets in the proper order for the requesting processor. { 6,7, // rpm 18,19, // map 20,21, // mat 22,23, // clt 24,25, // tps {0} } }, {0} };
The last {0} tells the compiler to pad out the rest of the structure with 0s, however additional messages could be configured here, just like it is possible to change the coolant table for a non-GM sensor. That table is also a flash-only table, but it can be changed in MegaTune and remains in the processor thereafter.
The above outmsg structure data tells MS-IITM that when a request for message no. 0 is received, it should extract and send rpm, map, air temp, coolant temp, and throttle position to the GPIO device.
The request would be made in GPIO as follows:
/******* sample message setup (placed in gpio mainloop or in a ISR, eg. timer) ****** ix = can[0].cxno_in; can[0].cx_msg_type[ix] = OUTMSG_REQ; // Put data from MSII in this gpio block can[0].cx_destvarblk[ix] =The 10 bytes would then be sent by MS-IITM as 2 messages and received as such by GPIO which would put the 10 bytes in that order into its own structure made for that purpose.; // from tableDescriptor can[0].cx_dest[ix] = 0; // send to MS II (board 0) // Below is offset from start of gpio block - it should // always be 0. If more than 1 msg, MS2 will send offset // to count byte sent can[0].cx_destvaroff[ix] = 0; can[0].cx_outmsg_no[ix] = 0; n_inbytes[ ] = tables[ ].n_bytes; send_can(0); *******************************************************************/
Note: the outmsg structure is placed in, but not really used in GPIO. GPIO creates a separate structure that contains only the variables it wants from the MS-IITM outpc structure in the same order as specified in the outmsg configuration. Then MS2 just sends the bytes in that order and they are put directly in the GPIO structure. It only needs to be included because it is referenced in the CANbus ISRs, but the sections where it is referenced will only actually be entered in MS2, not in GPIO. If these references are removed, then the outmsg structure can be removed from GPIO. If it is kept in, you can leave the default data as all 0s since it will never be actually addressed in GPIO.
Operation
Each GPIO application that wants to take advantage of this must document the offsets and other data in the above structure and create a ram structure to hold the received message bytes in the same order as they are received.
For example, for the preceding sample message, the GPIO device could use:
typedef struct { unsigned short rpm; short map, mat, clt ,tps; } can_msvar;
which can be instantiated as:
can_msvar msvar;
and the data referenced as: msvar.rpm, msvar.map, etc. The msvar structure must be entered on the ram side in the const tableDescriptor tables[NO_TBLES] structure, so it will have a varblk number. (See setting can[0].cx_destvarblk[ix] =
The program sequence for all this to happen is as follows:
The user must enter message configuration data in the MS-IITM outmsg structure using MegaTune. The byte offsets are the same as in MT ini files. If there is more than one GPIO device in a user setup, the user must assign different msg_nos to the second device, say 1 and 2, if it has 2 OUTMSGS and msg_no 0 is for the first device. The no. bytes and offset for these msg_no s are entered in the MS2 outmsg structure, so when MS2 receives a request for msg_no 1 it knows to send the bytes at outpc + outmsg[1].offset[ix], ix = 0, outmsg[1].no_bytes -1. This is very efficient, flexible and fast. For the near term a good message set that will cover most everyone can be set as the default, so users won’t have to do anything, but as devices proliferate, the structure can be exposed as fully modifiable.
MegaSquirt® and MicroSquirt® controllers are experimental devices intended for educational purposes.
MegaSquirt® and MicroSquirt® controllers are not for sale or use on pollution controlled vehicles. Check the laws that apply in your locality to determine if using a MegaSquirt® or MicroSquirt® controller is legal for your application.
©2005, 2007 Bruce Bowling and Al Grippo. All rights reserved. MegaSquirt® and MicroSquirt® are registered trademarks. This document is solely for the support of MegaSquirt® boards from Bowling and Grippo.