zencan-node
Crate for implementing a zencan device. Usually used in an embedded no_std context, but also can
be used elsewhere.
Usage
Create a device config
First create a TOML file to define the properties of your node. This includes information like the device name and identity information, as well as a list of application specific objects to be created. All nodes get a set of standard objects, in addition to the custom ones defined in the device config.
Sample device config
= "can-io"
# The device identity includes the vendor, product, revision and a serial
# number. The serial number should be unique among all device instances, so it
# is provided by the application at run-time -- e.g. from a value stored in
# flash, or a MCU UID register.
[]
= 0xCAFE
= 32
= 1
# PDOs are used for sending "process data". Values which are transferred
# frequency on the bus, such as control commands or sensor readings, are
# typically sent and recieved using Transmit PDOs (TPDOs) and Receive PDOs
# (RPDOS). These are configured at run-time by writing to the appropriate PDO
# configuration objects, but the number of PDOs which can be configured must be
# defined here, at compile time.
[[]]
= 4
= 4
# Create an array object to store the four u16 raw analog readings
# It is read-only, so it can only be updated by the application
# It can be mapped to transmit PDOs for sending data to the bus
[[]]
= 0x2000
= "Raw Analog Input"
= "array"
= "uint16"
= "ro"
= 4
= [0, 0, 0, 0]
= "tpdo"
# An object for storing the scaled analog values
[[]]
= 0x2001
= "Scaled Analog Input"
= "array"
= "uint16"
= "ro"
= 4
= [0, 0, 0, 0]
= "tpdo"
# A configuration object for controlling the frequency of analog readings
[[]]
= 0x2100
= "Analog Read Period (ms)"
= "var"
= "uint32"
= "rw"
= 10
# A configuration object which can be used to adjust the linear offset and scale transform used to
# calculate the value in 0x2001
[[]]
= 0x2101
= "Analog Scale Config"
= "record"
[[]]
= 1
= "Scale Numerator"
= "uint16"
= "rw"
= 1
[[]]
= 2
= "Scale Denominator"
= "uint16"
= "rw"
= 1
[[]]
= 3
= "Offset"
= "uint16"
= "rw"
= 0
Add zencan-build and zencan-node as a dev-dependency
zencan-build contains functions for generating the object dictionary code, and can be used as a
build dependency.
cargo add --build zencan-build
zencan-node is compiled in as a normal dependency.
cargo add zencan-node
Build the generated code in build.rs
In your build.rs script, add code like this:
Include the generated code in your application
When including the code, it is included using the name specified in build -- ZENCAN_CONFIG in this
case. This allows creating multiple object dictionaries in a single applicaation.
Typically, an application would add a snippet like this into main.rs:
Instantiate a node
// Use the UID register or some other method to set a unique 32-bit serial number
let serial_number: u32 = get_serial;
OBJECT1018.set_serial;
let node = new;
Feed it incoming messages, and poll process
Since Zencan doesn't know what your CAN interface looks like, you have to do some plumbing to wire it up.
Received messages can be passed in using the NODE_MBOX struct, which serves as a Sync buffer
between the receive and process contexts. For example:
NODE_MBOX.store_message?;
The node process() method must be called from time to time. It isn't critical how fast, but your
node's response time depends on it. The NODE_MBOX also provides the set_process_notify_callback
method. This can optionally be used to register a callback for whenever there is new information to
be processed, so that the process() call can be accelerated by the application.
Here's an example snippet which uses lilos and a Notify object -- which is set by a register process notify callback function to trigger the process task to run immediately on process, or after a 10ms timeout when no callback is received.
/// A task for running the CAN node processing periodically, or when triggered by the CAN receive
/// interrupt to run immediately
async
Socketcan Example
You can also run a node on linux, with socketcan. This can be useful for testing with a virtual can adapter. See this example for a full implementation.