I’d typically just send everything that might be useful but have some frames that are less frequency informational ones. It can be a bit annoying keeping the frame definition updated, although if this is just a point-to-point link then that’s less of an issue. Once you’ve got half a dozen different devices on a CAN link all sending different stuff at different times from different code bases it can get a bit squirrely.
In general, it’s easiest to add data to a partially full frame, 2nd easiest to add a new frame, hardest is to reshuffle an existing frame and change data order etc.
So typically I would try to avoid statefulness as much as possible. If you want sensor data every 100ms then I would just have the thing that’s doing the sensing just start up and free run, sending it every 100ms. If you have something where sometimes the sensor data isn’t valid, I would either set up a state code that can be sent along or a set of flags. Something like a byte that can enumerate to Normal, Error A, Error B, etc. Or if it’s just a set of conditions, each could be a binary flag like Running = true/false, Overtemp error = true/false, Initialized = true/false, homed=true/false etc. Picking what to do there is usually a case of what you’re trying to accomplish and is usually obvious which is most suitable. If there are only a couple of binary states but without any commonality then flags are easiest. If there are a bunch of specific states where only one can exist at a time, a byte with specific numbers for each code would be best. If there is a mix then it could be something like Running = True/False, Error = True/False and then a separate error code, 0x00 no error, 0x01 overtemp, 0x02 overvolt, 0x03 etc. etc.
If you’re trying to control something, having a message that’s from controller to sensor that has a command flags or a command byte also works. The easiest/cleanest is to have something that makes sense to be sent repeatedly over and over, like Command = Run/Stop/Init, etc. and then step through the states on the controller according to feedback. You can also treat it as something that gets sent once like you send a single message that says ‘Init’ as the command to trigger an initialisation but that’s a little bit ugly. I would prefer to do it as something where you constantly send the command and then track the state on the sensor side.
Yeah, you’ll need separate messages for controller to sensor and sensor to controller.
So as an example if you had something where you were controlling a CNC machine:
The CNC machine could have a frame that it sends that has a bunch of flags for initialized, homed, moving, error and then XYZ position. It could have another frame that it sends for spindle state which has RPM, error flag, spindle temp.
The controller could have a frame that it sends that is a command byte and then 7 payload bytes according to the command. It only sends that frame once per command but needs to receive an ack. It could instead have a fixed frame that it sends a command byte, XYZ position, spindle speed all crammed into one frame. Doing it that way you could have a ‘no command’ message which is just spammed to be ignored. That would give you ACK feedback constantly without sending things. You could do it such that you send the command as ‘Home’ once and then no-commands while the machine is homing. You could do it such that you send the command ‘Home’ constantly and the machine knows to start homing when it goes from any other command to home but then ignore additional home commands beyond that, etc. etc.
It’s all just a case of thinking through the flow of how you want it to work and trying to consider all the ways it could tie itself in knots (like the controller sends the Home command constantly, the machine tries to home, sends a ‘homed’ flag once it’s done, receives the Home command again and re-homes, so the system oscillates) or miss a certain combination of conditions then plan for those. It’s best to make it inherent, if possible, such that a sudden drop in messages or a stuck message won’t break things with the 2nd best being to program in special cases to handle weird conditions like the repeat homing thing etc.
Another thing you can do is choose whether you want in-band or out-of-band error signalling etc. Like for instance if you can have errors for each axis you could do them as a set of binary flags, errorX, errorY, errorZ. You could have a single byte that’s just ‘error’ and has error codes like 0x01 = errorX, 0x02 = errorY etc. That gives you the option for more information errors (like X overtemp, X undervoltage, X unexpected home trigger, Xmax limit, Xmin limit etc.). You can do in-band signalling like having valid positions for Xpos be 0x0000 to 0xFF00 and then having errors be 0xFFFF, 0XFFFE etc. That can be a nice and efficient way of transmitting those errors, but it needs a lot more care to avoid mistakes like having an error that’s a valid negative position in the case of an unhomed movement or something where a value gets checked whether it’s in error, gets updated in an interrupt to go from a valid position to an error and then gets processed as if it’s a valid position etc. etc.
All depends on your goals. CAN and RS422 are a great pair of technologies to be familiar with because they accomplish the same thing in vastly different ways. If you want something that’s basically a robust serial port, RS422 is awesome. If you want something that is just shuttling numbers around and the protocol/checksum/acknowledgement/arbitration is all handled for you then CAN can be great.
There’s also a lot to be said for using whichever one seems the most interesting to learn about!