I propose adding control channel data reception to the Cyphal protocol. This is a common use case, and would be a good fit for various vehicle types that use Cyphal. This is a collaboration between Hydra and myself.
Requirements:
-
The device transmits control channel data with low latency. The canonical use case is a radio transmitter connected to a manual controller, with 4 high-resolution channels for pitch, roll, yaw, and throttle, and several more lower-resolution “aux” channels assoc. The aux channels are intended for buttons, and switches that have fixed positions. They might be used for changing flight mode, cycling steer points, activating payloads, arming motors etc. This is an example use and is common, but a more general solution is desired.
-
The device sends link statistics, such as RSSI, portion of packets received recently, transmitter nominal power level etc. Note on a general solution applies here as well.
-
The device sends metadata, including an immediate message if the link is lost. (Note: This could also be handled by the receiver, but I think a backup from the RF node itself would be nice) It may also send a periodic heartbeat containing system status.
I’m unclear on the exact capabilities of DroneCAN and Cyphal, and use cases that may arise. Below are two starting example implementations, that represent different ends of a flexibility spectrum. They would both be sufficient for the canonical use case described above, but different in applications beyond it:
#1: A simple, partially hard-coded standard, analogous to the CRSF protocol. I imagine this will integrate in a straightforward way with DroneCAN. I’ve put this together by following examples on the DroneCAN List of standard data types. Of note, it sends all control channel data in the same packet, without distinguishing priority.
Example packet, using the DroneCAN List of standard data types:
uint16 [<=10] channel_high_precision # High precision channel data. For example, throttle or pitch
uint8 [<=10] channel_low_precision # Low precision channel data. For example, arm status or flight mode
Of note, I’m not sure how to make precision flexible using the example standard data types; in this example, I’ve hard-coded 2 precision levels. Compared to existing standards that use 11-13 bits of data for control channel data, this is higher precision, so uses more bandwidth/larger packet size than required. How would you handle this, using the DroneCAN spec?
Example link stats packet, sent at a slower rate, hard-coded values and precision; this is what CRSF uses. I am not proposing it specifically, but it’s an example of hard-coding, using a current standard. Of note, it is inflexible:
uint32 timestamp
uint8 uplink_rssi_1
uint8 uplink_rssi_2
uint7 uplink_link_quality
int8 uplink_snr
uint8 active_antenna
int8 rf_mode
int4 uplink_tx_power
uint8 download rssi
uint8 downlink_link_quality
int8 downlink_snr
- If the link is lost at any point, the node immediately broadcasts a terse message of high priority.
Downsides of the above:
- everything is hard-coded, inflexible.
- impossible to add new data types or telemetry items without defining entire new frames.
- and change breaks backwards compatibility and introduces run-time or compile-time logic into flight-controller codebase.
- looks to the past, only at what exists now, not the future which stifles development and experimentation possibilities.
#2: An ideal, flexible standard. I would prefer something like this, but am not sure if it fits the model of DroneCAN and/or Cyphal:
- By default, the node is silent. The entity receiving data (eg flight controller) submits a discovery request. The Rx responds with a list of information it can supply, including channel indices, available resolutions, and available data rates for each channel. Given this info, the FC can choose which info it wants to receive based on the Rx’s capabilities, which it knows due to the response to discovery. The subscribe request will always succeed, since the flight controller has the information it needs to make it.
Example of available information: #1: The channel number (to be interpreted by the Rx node). #2: Data rate. #3: resolution. The node responds with a success or error message, per its ability to serve this request. If success, the node broadcasts the data as requested until it receives a cancellation request. Example subscription request:
[
Subscription index/reference = 1,
Rate: 100Hz,
Channel 1, 12 bits,
Channel 2, 12 bits
]
[
Subscription index/reference = 2,
Rate: 50Hz,
Channel 5, 2 bits,
]
[
Subscription index/reference = 3,
Rate: 10Hz,
Channel 6, 2 bits,
Channel 17, 1 bit,
Channel 18, 1 bit,
]
Example responses here. E.g.
[
Subscription index/reference = 1,
<12 bits of data for channel 1>,
<12 bits of data for channel 2>,
]
[
Subscription index/reference = 2,
<2 bits of data for channel 5>,
]
[
Subscription index/reference = 3,
<2 bits of data for channel 6>,
<1 bits of data for channel 17>,
<1 bits of data for channel 18>,
]
The sequence of packets will be something like this:
1,1,1,1…,2,1,1,1…,2,1,1,1… eventually 3,1,1,1…, 2,1,1,1…
The flexible approach would be robust to changing requirements for different use cases, and allow the subscriber (e.g. flight-controller) to only receive the data it needs, at the rate it’s capable of receiving. It would minimize the amount of data sent, saving bandwidth. Of note, the FC could make multiple subscriptions, each with a reference that is included in the subscribe request; the same reference is returned when the Rx sends data, so the FC knows what data, and the format of it for decoding purposes.
I think some combination of the approaches may be viable. For example, a flexible subscription setup, but restricting the number of data rates to 2.
Of note, I think for many cases of channel data, 2+ frames may be required on FDCAN. If this addition is made available for basic CAN, multi-frame packets would be required in all cases. This begs the question: Should this be backwards compatible with basic CAN, or should it be FDCAN only? Suggested approach: A discovery agreement between the Rx and FC, allowing either to be used. The subscriber can choose the frame format (CAN/FDCAN) too.
Relevant discussion on the TBS github; about improving the CRSF spec, and is immediately applicable here: CRSF Protocol Repo · Issue #26 · tbs-fpv/freedomtx · GitHub
Would appreciate any and all input on establishing a standard that’s simple and flexible. Of note, I already have a working CAN receiver, using an ExpressLRS circuit integrated with an STM32 MCU that acts as the FDCAN node, and a CAN transceiver. Using this for specific hardware where both Rx and flight controller are cooperative is easy; my intent here is establishing a common API that will allow CAN Rxes to be swapped arbitrarily, with no change to flight-controller code.
Key design requirements/features:
Flexible
Additions to telemetry data points should not require a protocol change.
Additions to Rx data points should not require a protocol change.
Should not force users into legacy data items, such as the notion of ‘uS-based’ channel values.
Minimal or zero error handling requirements in the subscriber/flight-controller core.
Would be nice:
Re-use of existing code/technologies in the flight-controller core.
In-faillable subscription requests (requires conveying maximum number of subscribe requests,maximum unique data rates, etc, in the ‘discover’ phase).