Creating RawCAN Frames with MicroBasic Scripting

1 year 5 months ago #29535320 by alhaddad
I'm new to CAN Bus and scripting and I collected a general idea about CAN Frames. But I still don't know how I shoud write a script for RawCAN Frames.
So lets say I wanna creat a 64-bits CAN message with a CAN ID and multiple signals/commands inside the Frame. In CAN Networking manual it's written in Transmitting Raw Frames that we write the Command CS to send CAN Frames (for example setcommand(_CS,3,5) etc...). So basically I want to send the command P to my motor to go to an absolute position and the command S for setting the motor speed. How can I write that in my script?
The same problem goes to checking and reading received Frames.
PS: My script is to be written so it can work without being connected with PC (via RS232 or USB).

I would much appreciate any help by an explanation or/and sample scripts (I didn't find a sample script with RawCAN)

Please Log in or Create an account to join the conversation.

1 year 5 months ago - 1 year 5 months ago #29535323 by LROBBINS
Perhaps a couple examples will help.  These are from a pretty complex script that's part of a multi-node CANbus wheelchair control system.

With RawCAN you are responsible for defining almost all aspects of the message packet which contains an ID field and up to 8 bytes of data.  The basic CAN protocol says that the ID field should tell other nodes how to handle the message and you have 11 bits for doing that.  Roboteq by default uses the ID only for specifying "accept messages only from node n" or "accept messages from all nodes".  I configure the Roboteq to accept messages from all nodes to let me build an ID that gives me more flexibility in filtering messages. 

This requires some bit-manipulation to create the ID field.  In my system, the ID is divided into 3 sub-fields:
     3 bits are used to specify either who's sending or who should respond to the message. (with 3 bits, only 8 nodes can be specified)
     2 bits are used to say which action group this message falls into: 0b00 = SetCommand, 0b01 = GetValue, 0b10 and 0b11 = two groups of SetConfig and GetConfig commands.
     and 6 bits contain the name of the specific command.  Most of these match the standard Roboteq names, such as _G or _GO, that you can find in the manuals or listed concisely in the Constants.h file of the Roboteq API.  I have also added some names that are specific to my application.

The basic CAN protocol and the controllers that implement it already have fairly robust checks that messages are intact before they are acted on (cyclic redundency check, bit timing checks, prioritization and disambiguation rules and re-sending until the recipient sends an ACK saying all is well), but because mine is a safety-critical application, I've added another layer to this.  Each message can contain up to 8 bytes of data.  I use only 0 to 4 of these for actual data, and use the others for the bitwise complements of the data bytes.  The receiving node then checks that (data + complement) = 0XFF before accepting the message as intact.

Here's an example of sending a message from the Roboteq MicroBasic script.  It just sends the value of _FLTFLG if there's a controller fault.

First, the RoboteqFault_id ID is assembled from it's 3 sub-fields:
DIM toMaster as INTEGER
DIM RoboteqFault_id as INTEGER
toMaster = 0b101
Value = 0b01
RoboteqFault_id = (((Value << 6 OR _FLTFLAG ) << 3) OR toMaster) ' _FLTFLAG is a standard Roboteq Constants.h defined number; it happens to be 21 decimal, or 0b010101

The subroutine then loads just two bytes: the value of _FLTFLAG (if there's a fault) in SendData[0] and the complement of that in SendData[1].  A series of _CANSEND commands then populate the message ID and data fields and sends the message:
'Roboteq Fault Flags (_FF or _FLTFLAG)
'f1 = overheat (bit 0)
'f2 = overvoltage (bit 1)
'f3 = undervoltage (bit 2)
'f4 = short circuit (bit 3)
'f5 = emergency stop (bit 4)
'f6 = Sepex excitation fault (bit 5)
'f7 = MOSFET failure (bit 6)
'f8 = startup configuration fault (bit 7)
  FaultFlags = GetValue (_FLTFLAG)
  IF (FaultFlags <> 0) THEN
    IF (FaultFlags <> LastFaultFlags) THEN
'LastFaultFlags is defined in a confirmation message received from the rest of the system, so we'll stop sending this CAN message if it's already been received
      SendData[0] = FaultFlags
      SendData[1] = (0x000000FF AND (NOT (SendData[0])))
      SetCommand (_CANSEND, 1, RoboteqFault_id)
      SetCommand (_CANSEND, 3, SendData[0])
      SetCommand (_CANSEND, 4, SendData[1])
      SetCommand (_CANSEND, 2, 2)
    END IF
Receiving a message involves parsing the 3 sub-fields from the ID and checking that the data and complement bytes add up.

  WHILE (GetValue (_CF)>0)
    ID = GetValue (_CAN, 1)
'extract target sub-filed, if target = toRoboteq handle confirmation handshake messages or handle incoming messages, otherwise ignore it
    Target = ID AND 0b111 'target mask
    IF (Target = toRoboteq) THEN
'handle incoming messages: retrieve data and parse ID to get Category and Name, and set MsgReceived to TRUE
'probably not necessary, but clear old data out of array
      FOR i = 0 AndWhile (i <= 7)
         ReceivedData = 0
'extract data from message
      NumData = GetValue (_CAN, 2)
         IF (NumData > 0) THEN
           FOR i = 0 AndWhile (i <= NumData-1) 'changed from NumData so can't go out-of-range
             ReceivedData = GetValue (_CAN, 3+i)
       END IF
'check for bit-wise complement integrity
       Pairs = NumData/2
       Check = TRUE
       IF (Pairs > 0) THEN
           FOR i = 0 AndWhile (i <= Pairs-1)
              IF (ReceivedData + ReceivedData[i+pairs] <> 0xFF) THEN
                Check = false
              END IF
              IF (Check = false) THEN
                EXIT FOR 'exit the FOR loop as soon as one data+complement don't add up to 0xFF
              END IF
        END IF
'if all OK, extract Category and Name and set MsgReceived to TRUE
        IF (Check = TRUE) THEN
           Category = (ID >>9) AND 0b11 'category mask
           Name = (ID >> 3) AND 0b111111 'name mask
           MsgReceived = TRUE
           GoSub PROCESS_MESSAGE 'a subroutine that starts doing whatever the message says needs to be done
        END IF
     END IF 'handling confirmation messages or
   END IF '(Target = toRoboteq)
 END WHILE '(GetValue (_CF)>0)

The Roboteq manuals have a wealth of information, so they're well worth reading more than once.  CAN is pretty involved, however, so I also strongly recommend two documents that you can download from Microchip.  They are: AN713 Controller Area Network (CAN) Basics and AN228 A CAN Physical Layer Discussion.  The first will help you understand how CAN messages are constructed and handled, the second will help you understand the hardware that's involved and help you avoid some of the pitfalls especially if you will be communicating with some non-Roboteq nodes.


Please Log in or Create an account to join the conversation.

1 year 5 months ago - 1 year 5 months ago #29535331 by alhaddad
Hi @LROBBINS and thank you for your answer,
I understood the structure of the scripting for sending and receiving CAN frames. However there is still something I'm not sure of.
In sending a CAN frame you worked on a runtime query. However I was trying to send a runtime command in SendData[0] and its complement in SendData[1] but I wasn't able to write a proper code for it. How could be this written in the script. Moreover you corresponded SendData[1] to a complement as a hexadecimal number, so my question is how do you refer it to the action that should be taken? In other words if I have for example the command with syntax scripting ''!P 1 1000'', how can I transform it to a CAN transmitted frame on MicroBasic Scripting?

Additional question on the side:
I'm using FBL2360T and I'm just able to run the motor using the syntax scripting in the console (I just could make the motor rotate using the command !S) in open loop mode, when I configure it to to any closed loop mode it doesn't work, even when I tried to change the loop error and using a lot of variables (RPM). What am I missing here?

Thanks in advance

Please Log in or Create an account to join the conversation.

1 year 5 months ago #29535332 by LROBBINS
You can't specify what to do with SendData[0] SendData[1] in those data bytes.  You have to specify that somewhere else, either in the ID field as I do or in some additional data bytes. 

I use the ID field and ignore the default way that Roboteq uses the ID field.  The example shows, at the top, how I build that ID.  The CK_FAULT: subroutine shows one example of how that ID is put in the CAN message, and the RECEIVE_MESSAGE: subroutine shows how that ID gets used.  RECEIVE_MESSAGE: first extracts the three "target" bits first to decide whether it's a "to Roboteq" message.  If the "target" is "to Roboteq" It then checks that each data byte and each complement byte sum up correctly.  If that's all OK, it then extracts the two bit "category" and 6 bit "name" fields from the ID.  Other subroutines then use "category" and "name" to decide what to do, in many cases using switch structures.  One of those is PROCESS_MESSAGE:. 

Every time the PROCESS_MESSAGE: subroutine is called, it does a Roboteq command or calls one or another subroutine based on what's in Category and Name and what has to be done with the data.  For example, if Category = Command and Name = SEATCMD, it just does SetCommand (_DRES,ReceivedData[0]) to reset the digital output flags bits according to whats in ReceivedData[0].  Notice that it doesn't use the complement that's in ReceivedData[1] - that was just used to check that the message hadn't been corrupted.  In most cases, what I have to do with the data is more complicated, so PROCESS_MESSAGE: calls a subroutine that carries out a series of calculations and/or Roboteq _xxxxx commands.

  MsgReceived = false
  SetTimerCount(0, CANwatchdog) 'reset CANwatchdog each time joystick message received
  SetTimerState(0, 0) 'start timer
  IF (Category = Command) THEN
    IF (Name = _GO) THEN
    ELSEIF (Name = _DSET) THEN
    ELSEIF (Name = POT) THEN
      GoSub SET_POT
    END IF
      SetCommand (_DRES,ReceivedData[0])
  ELSEIF (Category = Value) THEN
    IF (Name = _FLTFLAG) THEN
      lastFaultFlags = FaultFlags
      lastStatusFlags = StatusFlags
      GoSub GET_DIGOUT
    END IF      
  ELSEIF (Category = Config) THEN
    IF (Name = _RWD) THEN
    ELSEIF (Name = _MXPF) THEN
    ELSEIF (Name = _MAC) THEN
      GoSub SET_ACCEL
      GoSub SET_BOOSTS
      GoSub SET_PINS1
      GoSub SET_PINS2
      GoSub SET_MOTORS
    END IF
  END IF '(Category = Command elseif Value elseif Config)

If you want to keep Roboteq's use of the ID to specify a single sending node from which it will accept messages, you will have to use one or more added data bytes to specify what kind of message it is.  For example, you could do something like this:

Data[0] = a number defining what kind of script action is involved (GetValue, SetCommand or SetConfig), but you have to interpret that byte to know which is meant.
Data[1] = a number saying what _xxxx command you want (if you use the same numerical values as Roboteq specifies in the API, no interpretation is needed).
Data[2] and Data[3]= whatever additional information is needed by the _xxxx  command, e.g. channel(s), motor command value etc.  If more than one value is needed, or if a needed number is longer than a byte, or if it must be signed, you will need to do some bit banging to get the actual numbers to use in the _xxxx command.

If all of the needed information fits in those 4 bytes, you can use the other 4 bytes for complements as I do, but if you need some 32 bit signed integers you will have to use more data bytes for that.  Or you can divide the information into more than one message, or, for example, because not all bits in Data[0] are needed, you could pack some more information there, and then bit bang to get it back out.  In American English we'd say "There are many ways to skin a cat", but it's really up to you to define how you want to do it.  And you may need to think about trade offs - bit packing and unpacking takes more calculation time, dividing information into more messages takes more CAN time.

I can't offer any help for your question about the FBL controller.  I am using only brushed motors and open loop so have no experience with this.  I have read the manual for the rest, but certainly haven't put any of that in my limited biological memory.

Please Log in or Create an account to join the conversation.

1 year 5 months ago #29535336 by alhaddad
Thank you so much! One more question, does RawCAN support the integration of DS402 in the script, in other words does my motor respond to DS402 commands written in my RawCAN script when I enable the function in the configuation, or does it just support CANOpen?

Please Log in or Create an account to join the conversation.

1 year 5 months ago #29535341 by LROBBINS
DS402 is a CANOpen protocol defined in IEC61800-7-200 (CiA402).  For each device that impements a CANOpen protocol the supplier defines an Object Dictionary with codes for each of the commands that is included.  Different devices from different manufacturers will have different object dictionaries.  Although it might be possible to write a RawCAN program that mimics or interprets a given Object Dictionary that would require knowing what's in that dictionary and exactly how the CANOpen messages are structured.  In other words, you would either have to "sniff" out what's in each of the messages and/or delve quite deeply into the CANOpen documentation.  Note that although CANOpen is "open", the documentation is quite expensive.  At this point in your learning curve I'd urge you to not get involved with this and I can also offer no help with it should you decide to go this route.

Please Log in or Create an account to join the conversation.

1 year 1 month ago #29535435 by alhaddad
I would like to use your expertise in the same topic in MicroBasic Script. I managed to create the CAN interface for the FBL2360T controller successfully, and I am establishing the CAN communication with a control unit. My goal is to run signals on different task rates, respectively 1ms and 10 ms. The control unit is a MATLAB/Simulink- based software development so I arranged transmitted and received signals for the CAN interface in integrator models with the wished task rates based on my application.
  • The problem lies now in script of RoboteQ and specifally with the task rate. I am aiming for an execution loop time for the script of 1ms, however the loop time is not constant and I get different task rates for each transmitted signal. I tried the statment "wait(1)" for a waiting time of 1ms, but it just adds up to the already existing time difference. I sent a simulation in the attachement for and incremental value for a better visual explanation. 
  • Another problem is synchronizing the tasks rates on the 2 devices to have the same setpoints (same start and end time of each task). What is the way to to adjust them in a sync time, so no error or delay occur in the execution?
I appreciate your technical support and would like to thank you in advance.


Please Log in or Create an account to join the conversation.

1 year 1 month ago - 1 year 1 month ago #29535436 by LROBBINS
I am sorry to have to tell you that I don't think that there's any way to get a MicroBasic script to run at a constant loop cycling time no less to synchronize two processes running at different rates.  The MicroBasic interpreter is a low-priority sub-system of the microprocessor which first allots computer clicks to basic motor control functions, and then gives a "slice" of each 1msec, if any is free, to the interpreter.  A simple MicroBasic script with a single loop may complete what it has to do in that "slice", but the slice will not always be the same length, sometimes the whole 1msec will be used for essential motor control functions, and if the script is complex even a single cycle through a loop may be split between pieces of different 1msec intervals.  All this makes things done in MicroBasic scripts quite asynchronous and you have to structure what you're trying to do so that synchrony isn't needed.

Please Log in or Create an account to join the conversation.

Time to create page: 0.121 seconds