CANBus protocol design

Status
Not open for further replies.

ilium007

Well-known member
Hi - I am back on my model train control project after walking away from it for 4 months ie. I have forgotten everything I had research last year !!!!

** I am not undertaking this to find a simple solution, I want to learn more about electronics, coding and design and I'm using this project (for my Dad) to achieve this**

I am going back to basics trying to work out how I communicate between modules I am going to be building. I have a requirement for a LOT of I/O and had previously looked at, and prototyped, shift registers and the various MCPxxxx devices looking to expand I/O with decent current sink capability.

I am going to look at using the NXP PCA9698 which gives 40 I/O controlled via I2C and with the ability to sink 1100mA which is perfect for what I'm doing. When I originally looked at this I worked out that noise over extended I2C or SPI connections was going to be a problem along with fanout issues with large numbers of shift registers or MCPxxx devices. RS485 was ruled out because I wanted to be able to have any device talk on the wire and allow the hardware to handle arbitration.

I am going to try and prototype Teensy 3.2 MCU's, talking to NXP PCA9698's via I2C using CANBus to talk between the Teensy's as show below (more Teensy's and more NXP PCA9698's per Teensy in the actual project):


Code:
                                        +------------------------+
                                        |                        +------->
                               I2C      |                        +------->  OUTPUTS
                           +------------+       NXP PCA9698      |                       +-------------+
                           |            |                        |                       |             |
                           |            |                        +---------------------->+ SIGNAL B    |
                           |            +------------------------+                       |             |
                           |                                                             +-------------+
     +---------------------+--+         +------------------------+
     |                        |         |                        <------+
     |                        |   I2C   |                        <------+   INPUTS
     |      TEENSY 3.2 #1     +---------+       NXP PCA9698      <------+
     |                        |         |                        <------+
     |                        |         |                        <------+
     +---------------------+--+         +------------------------+
        ||                 |
        ||                 |            +------------------------+
        ||                 |            |                        +------>                               +----------+
        ||                 |    I2C     |                        |          MIXED INPUTS / OUTPUTS      |          |
        ||                 +------------+       NXP PCA9698      <--------------------------------------+ SWITCH A |
        ||                              |                        |                                      |          |
        ||                              |                        +------>                               +----------+
        ||                              +------------------------+
        ||
        ||
CANBUS  ||
        ||
        ||
        ||                              +------------------------+
        ||                              |                        +------->
        ||                     I2C      |                        +------->  OUTPUTS
        ||                 +------------+       NXP PCA9698      |                                      +----------+
        ||                 |            |                        |                                      |          |
        ||                 |            |                        +------------------------------------->+  LED A   |
        ||                 |            +------------------------+                                      |          |
        ||                 |                                                                            +----------+
     +---------------------+--+         +------------------------+
     |                        |         |                        <------+                    +-----------+
     |                        |   I2C   |                        |              INPUTS       |           |
     |      TEENSY 3.2 #2     +---------+       NXP PCA9698      <---------------------------+ SENSOR B  |
     |                        |         |                        |                           |           |
     |                        |         |                        <------+                    +-----------+
     +---------------------+--+         +------------------------+
                           |
                           |            +------------------------+
                           |            |                        +------>
                           |    I2C     |                        |          MIXED INPUTS / OUTPUTS
                           +------------+       NXP PCA9698      <------+
                                        |                        |
                                        |                        +------>
                                        +------------------------+

This diagram just shows I/O - I will have more CANBus connected Teensy's doing PWM for speed control and MOSFET lighting control but the idea is similar to the above.

My problem at present.... working out a 'simple' messaging format for use over CANBus.

Everything I find on the net searching for "arduino canbus", "teensy canbus" etc relates to integrating with motor vehicles.

Thinking about message ID length - 11bit ID's would be plenty for my use case, but do I break up the 11bits into a message 'type' as well as represent a source device ? I know CANBus is a message driven architecture ie. it doesn't matter where the message comes from or is going to - it is a broadcast message. The lower the ID the higher the message priority but why would one Teensy have a higher priority message tha

Refer to the diagram above - lets say SWITCH A triggers an input on a PCA9698, this in turn would cause the Teensy #1 to generate a CANBus message and broadcast it, Teensy #2 should see a message, process it and turn on an LED.

Now..... this is where I get stuck.... What is Teesny #2 looking at to decide whether or not to do something ? Is it based on the 11bit message ID or it is looking at the 8 bytes of data and looking for a 'message type' (would be a part of my little protocol)? If we split up the 8 bytes into, say, byte 1 telling it that it is an I/O frame (vs a speed control frame, or a lighting frame etc etc) and the following 7 bytes describe what should happen next - time of output, type of output (flashing, dim, steady etc) . If I did this then every CANBus node would be looking at every packet and that could be thousands per second.

Should a CANBus message only describe an event that has happened ? Thinking about a car... I press the door lock button, a message is probably sent on the 'body bus' stating the button has been pressed, a door lock solenoid then triggers based on the message and everything thing else on the bus ignores the message as it is not relevant to anything else but the door solenoid. How is that achieved - how do other MCU's ignore the door lock message ? By message ID ? By something in the 8 byte data field defining a message type (door lock type) or by something else ?

The next issue is if an inout on Teesny #1 needs to trigger an output on Teesny #1 - does it send the CANBus message to itself ? Can it read a message sent to itself ? Or do all inputs have to be on one Teensy and all outputs on another ?

Given the 'messaged oriented' architecture of CANBus what is the best way to get started thinking about the Teensy's communicating - I am trying to move my mind away from point to point based messaging ie source / dest addresses and think more about messages and message types.

I just need some pointers to get started to then go and start prototyping. Any help appreciated !
 
I have a small amount of experience from my current project, like you I needed to read distributed I2C and created a RS485 Arduino network, then converted to a Teensy Can Bus network.

You have the freedom to choose how you organise your network and message hierarchy (assuming you never need to integrate with someone else's Can Bus hardware.)
It is common for ID to relate to physical location (ie a Node on the bus has its own ID) in order that it's messages have a 'fixed' priority. This means that the vehicle turn indicators can always get its message on the bus ahead of say for example, the rear windscreen washer pump. However it is also common for a node to use a group of IDs within a range (Steering column stalk switches for example) and also use ID with much higher number (lower priority) for status and error reporting messages. It is also common to find the first one or two bytes in the 8-byte payload message are used to define the content command, response, configuration, status etc)

Can Bus uses Masks and Filters to allow nodes to 'ignore' messages that are not relevant to that particular node. However I would not recommend using these to start with as it adds an additional layer of complexity that can really slow down development, making it harder to trouble shoot when messages are not getting though.

If your network is short enough (from memory, less than 10 meters) you can run at 1Mbit which will give you a maximum ~7000 messages a second on the bus, try to keep below 80% of maximum to avoid latency issues. If your bus wiring is not twisted or is longer, or is running in a noisy environment you might have to drop to 500kbit/s. If your messages don't need all 8 bytes, you can squeeze more messages on the bus. Bus capacity calculators can be found online, or you can collect bus statistics from one of your nodes if you are running close to the edge.

A node cannot see the messages that it puts on the bus, and hence cannot send a message to itself. You can easily act on a message before sending by software switch case before the message is sent. A node would not normally put a message on the bus that was intended for itself but if you have spare capacity/bandwidth on your bus it would probably be very useful to you as you will be able to monitor all messages from any point on the bus.

I would possibly use the first 3 bits of the ID as a 'priority of message' (giving 8 priority levels), the next 4 bits as 'Teensy ID' (thus maximum of 16 Teensy Can nodes) and the final 4 bits for NXP PCA9698 IDs (allowing upto 16 PCA9698's per Teensy). The first payload byte could be Message type designator, the second a Message ID, leaving 6 bytes for your Signal/input/output value. This will aid trouble-shooting for a dead node or a dead PCA9698.

I am guessing that 10240 input/outputs is overkill, hence you could push the message type and message ID into the 11-bit Can ID if for instance you merged Teensy and PCA9698 numbers and/or reduced the number of levels of priority. It really is up to you, but work out what you think is best before you start.

Keep a good record of your ID mappings. Buy a cheap Can Analyser or build one (an additional teensy node that just reads CAN to a serial monitor that is not constantly re-programmed during your development will really help you )

<edit> I would also recommend looking at Tycommander / TYtools as it really helped me when working with multiple Teensys concurrently.
 
^^ AWESOME ^^

This is a great help to get me started ! I just needed an initial kick along to give me the confidence to get going !
 
I would possibly use the first 3 bits of the ID as a 'priority of message' (giving 8 priority levels), the next 4 bits as 'Teensy ID' (thus maximum of 16 Teensy Can nodes) and the final 4 bits for NXP PCA9698 IDs (allowing upto 16 PCA9698's per Teensy). The first payload byte could be Message type designator, the second a Message ID, leaving 6 bytes for your Signal/input/output value. This will aid trouble-shooting for a dead node or a dead PCA9698.

@Darcy I have finally got around to getting the CanBus transceivers operating with the Teensy's (and collin80/FlexCAN_Library) and have boards being printed at OshPark for the PCA9698's. I am now getting back to the code.

I am going to implement the message protocol along the lines of what you have given me above. I am just not sure about "Message Type" and "Message ID".

attachment.php


I assume "Message Type" will be a value 0-255 to distinguish, say, a "points close" command vs a "light on" command, I don't know how to make use of a "Message ID" field. You mentioned above that it would assist in trouble-shooting a dead node.


Screen Shot 2018-06-02 at 11.10.37 pm.png
 
Thinking about this some more, if I treated the "Message ID" field as an ID for the switch input for example I would need to allow for 16 * 40 inputs to identify ie. 640 ID's which wouldn't fit in the 1 byte field. Should I increase "Message ID" to 2 bytes to allow for all possible input ID's on that board ?

This would mean 5 bytes available for payload (heaps for my project).

attachment.php


Screen Shot 2018-06-02 at 11.29.28 pm.png
 
why you need extra gpio over i2c? throw in a teensy 3.5/3.6 and you have more than 40gpio to use there...
 
Thinking about this some more, if I treated the "Message ID" field as an ID for the switch input for example I would need to allow for 16 * 40 inputs to identify ie. 640 ID's which wouldn't fit in the 1 byte field. Should I increase "Message ID" to 2 bytes to allow for all possible input ID's on that board ?

This would mean 5 bytes available for payload (heaps for my project).

attachment.php


View attachment 13940

I've been using the extended mode on CAN for some of my experiments, and what I have been able to come up with is using the ID field as a sort of meta-datagram address field for source and destination, which allows for a more mixed network. I even have the ability to have separated debug "streams" for each device, and have printf to CAN, which is super helpful. Of course I am using a totally different chip, but the Teensy 3.2 is the main gateway to the pc, for programming remote MCU, debugging, and it even includes message priorities.

Here is a brief set of defines and explanations. I hope you can find them useful.


Code:
// source address
#define  CAN_ME_SRC (0x00000010u) // this is the first MCU
#define CAN_DXI_SRC (0x00000020u) // this is the second
#define CAN_DXO_SRC (0x00000040u) // etc...
#define RESPER4_SRC (0x00000080u)
#define RESPER5_SRC (0x00000100u)
#define RESPER6_SRC (0x00000200u)
#define RESPER7_SRC (0x00000400u)
#define RESPER8_SRC (0x00000800u)
#define RESPER9_SRC (0x00001000u)
#define RESPER10_SRC (0x00002000u)
#define RESPER11_SRC (0x00004000u) // up to 11 

// destination address
#define  CAN_ME_DST (0x00100000u)
#define CAN_DXI_DST (0x00200000u)
#define CAN_DXO_DST (0x00400000u)
#define RESPER4_DST (0x00800000u)
#define RESPER5_DST (0x01000000u)
#define RESPER6_DST (0x02000000u)
#define RESPER7_DST (0x04000000u)
#define RESPER8_DST (0x08000000u)
#define RESPER9_DST (0x10000000u)
#define RESPER10_DST (0x20000000u)
#define RESPER11_DST (0x40000000u)
//  low priority
#define   CANLO_SRC (CAN_ME_SRC | 0x18000000u) 
// high priority
#define   CANHI_SRC (CAN_ME_SRC | 0x10000000u)
#define CANDBUG (CANLO_SRC | 0x00Fu)


static CAN_message_t msg;
blhostListener blhost;
// printf over CAN
extern "C" {

        // Needs to bail out as so not to get stuck!
        int _write(int fd, const char *ptr, int len) {
                CAN_message_t pmsg;
                int i;
                int pos = 0;

                pmsg.id = CANDBUG;
                pmsg.flags.extended = 1;
                pmsg.flags.remote = 0;
                pmsg.flags.reserved = 0;
                while(len) {
                        i = len;
                        if(i > 8) {
                                i = 8;
                        }
                        pmsg.len = i;
                        for(int j = 0; j < i; j++) {
                                pmsg.buf[j] = ptr[pos];
                                pos++;
                        }
                        Can0.write(pmsg);
                        len -= i;
                }
                return pos;
        }

        int _read(int fd, char *ptr, int len) {
                return 0;
        }

#include <sys/stat.h>

        int _fstat(int fd, struct stat *st) {
                memset(st, 0, sizeof (*st));
                st->st_mode = S_IFCHR;
                st->st_blksize = 1024;
                return 0;
        }

        int _isatty(int fd) {
                return (fd < 3) ? 1 : 0;
        }
}

With the above code, the Teensy is sent data that it can check the debug flags in the message, and know who sent the debug message. It then stores the input until either a newline or the buffer is full, and output to the virtual serial port with a prefix indicating which MCU sent the data.

The last 4 bits of the source address indicate the message type. 0xF is the lowest priority and the "debug" flag.
You also get to use the entire 8 bytes for your payload. You don't need to worry about out-of-order fragments if you wait for each packet to complete, they will be sent serially.

I'm not going to include the Teensy3 part of this because I am not really using the standard CAN library.
I'm sure from the above that anyone who has enough experience can figure out how to use it anyway.
 
I'm still working with pen and paper here in an attempt to get enough to start writing code and have a few more questions. Is there a requirement to check for acknowledgments for a sent message or is it good enough to send a message once and now it will be delivered ? ie. I have a digital input that should trigger a message when triggered. Do I send a message once and trust it will be delivered or do I have to poll for some sort of acknowledgment ?

The other thing I am still a bit confused on is SRC / DST in the example code above. I though the whole idea of the CANBus protocol was to do away with SRC and DST and just deal with messages. Think about a simple momentary switch input - it should generate a message on the CANBus to say that it has been activated. I thought that it should not care what happens from here, its job is to put a message on the CANBus to say it has been triggered, it would then be up to other MCU's to see that message and do something if their code allows for it. I thought about then using the hardware mailboxes to filter for certain message ID patterns to allow each MCU to only accept certain message types.
 
I'm still working with pen and paper here in an attempt to get enough to start writing code and have a few more questions.
That's fine. Always a good idea to make a plan first.
Is there a requirement to check for acknowledgments for a sent message or is it good enough to send a message once and now it will be delivered ? ie. I have a digital input that should trigger a message when triggered. Do I send a message once and trust it will be delivered or do I have to poll for some sort of acknowledgment ?
You are able to check if it was delivered, ACK/NAK is automatic. See the datasheet for those details.

The other thing I am still a bit confused on is SRC / DST in the example code above. I though the whole idea of the CANBus protocol was to do away with SRC and DST and just deal with messages. Think about a simple momentary switch input - it should generate a message on the CANBus to say that it has been activated. I thought that it should not care what happens from here, its job is to put a message on the CANBus to say it has been triggered, it would then be up to other MCU's to see that message and do something if their code allows for it. I thought about then using the hardware mailboxes to filter for certain message ID patterns to allow each MCU to only accept certain message types.
You can do it as message only based. I simply choose to use it as a sort of "mac address" like on a more traditional network, which is especially handy if you want to printf() over CAN and be able to direct the output to a particular display. You could place this info in the data section if you wanted as well, but I just found it easier to make it more like a traditional network for my uses, and I also update the remote MCU over can too, so it makes it easier if I can address a node directly to upload new firmware to it.
 
You can do it as message only based. I simply choose to use it as a sort of "mac address" like on a more traditional network, which is especially handy if you want to printf() over CAN and be able to direct the output to a particular display. You could place this info in the data section if you wanted as well, but I just found it easier to make it more like a traditional network for my uses, and I also update the remote MCU over can too, so it makes it easier if I can address a node directly to upload new firmware to it.

Remote update over CAN !! I asked about that in relation to the Teensy's a while back and was told (at least for this MCU) that it wasn't possible due to the bootloader or something. It was something I was really interested in as my project is for my Dad's model railway and he is located 2000km away from me ! It would have been nice to update firmware remotely. I have the Teensy's for prototyping but haven't yet decided if they will be in the final design due to this.
 
For the ARM Teensy's with bootloader chips the only PJRC support to date is over USB. Some have worked with on chip code to program - one of those solutions might be found that could be made to work - but a simple mistake you'll see noted can brick the Teensy.

I tested one Frank B started, reading code from SD cards. It could flash half of program space and seemed to be functional, but never got as polished perhaps as what others have achieved before it was parked.
 
Remote update over CAN !! I asked about that in relation to the Teensy's a while back and was told (at least for this MCU) that it wasn't possible due to the bootloader or something. It was something I was really interested in as my project is for my Dad's model railway and he is located 2000km away from me ! It would have been nice to update firmware remotely. I have the Teensy's for prototyping but haven't yet decided if they will be in the final design due to this.

As I said, the targets aren't Teensy products, they don't even have USB.
 
Status
Not open for further replies.
Back
Top