RoverCAN
RoverCAN was designed to send and receive data over the CAN bus (used in the rover to connect all custom PCBs).
Basic Idea:
- We want to be able to send data from any CAN device (Jetson, any of the boards) to any other CAN device
- This data is in the form of a struct that both devices know the structure and size of
- This struct is then
- serialized (converted to a stream/array of bytes)
- fragmented (CAN allows for 8 bytes per data frame, we might want to send more than 8 bytes)
- sent (transmitted over the physical CAN bus)
Less Basic Idea:
- 29 bit CAN Arbitration ID is broken up into Sendpoint (sp, 8 bits), Endpoint (ep, 8 bits), and Sequence number (seq_num, 13 bits)
- Structs are sent from Sendpoint to Endpoint
- Sendpoints are used to identify the sender of a struct. Some examples:
- Jetson
- Drive Board
- Arm Board
- Endpoints are used to identify where the sender wants the data to end up. Some examples:
- Drive command
- Battery cell voltages
- Sendpoints are used to identify the sender of a struct. Some examples:
- Nodes are very similar to ROS nodes
- Have an identity field of type Sendpoint
- Listen (similar to subscribe) listens to Structs received on particular Endpoint
- listen(Endpoint ep)
- In the background, the listen method creates a mailbox for structs received on the given endpoint so that they can be obtained when they’re received.
- Send (similar to publish) sends Structs to a particular Endpoint from the Node’s identity Sendpoint
- send(Endpoint ep, Struct data)
How listen() works
- Whenever a CAN data frame is received, it is converted to a StructFragment
- StructFragments are just like data frames, but the bitfields of the arbitration ID are separated into individual fields
- RoverCAN Nodes have a map of data structures called Bins, which are simply containers that you put StructFragments into
- Bins correspond to one specific Endpoint, and each Endpoint has a specific type of Struct that all CAN devices know how to decode.
- Bins are comprised of Sub bins that each contain StructFragments with the same Sendpoint
- When a Sub bin is full, the contents are defragmented, deserialized, and then a Struct is reconstructed from the binary data. Then, the Struct is put in the Mailbox where the user can grab it whenever they want.
How send() works
- Send simply serializes the Struct and loops through the bytes, constructing a new StructFragment every 8 bytes, filling in the fields as it goes, then converts the StructFragment to a CAN data frame and sends it over the wire.