CAN
Controller Area Network (CAN) is a serial bus with built-in error checking and automatic retransmission, widely used in automotive and industrial systems. The most common variant is high-speed two-wire CAN, but one-wire and LSFT (Low Speed Fault Tolerant) transceivers are also in use.
High-speed two-wire CAN requires termination resistors at both ends of the wire pair — typically 120 Ω each.

Many CAN devices, including our development boards, have built-in termination resistors.
NuGet packages: GHIElectronics.TinyCLR.Devices.Can for the API, and GHIElectronics.TinyCLR.Pins for pin name constants.
Bit timing
Every node on a CAN bus must run at the same baud rate, so the first thing you set up is bit timing. Bit timing is one of CAN's more involved topics — the table below covers common rates, and online calculators can help with non-standard cases.
SetNominalBitTiming()— sets the standard CAN bit rate. For CAN-FD it also defines the slower arbitration-phase rate.SetDataBitTiming()— only used for CAN-FD, sets the faster data-phase rate.
The propagationPhase1 argument combines the propagation and phase1 CAN timing segments into a single number.
The table below assumes a 40 MHz clock source:
| Baud | Propagation+Phase1 | Phase2 | Baudrate Prescaler | Sync Jump Width | Multi-Bit Sampling | Sample Point | Max Osc. Tolerance | Max Cable Length |
|---|---|---|---|---|---|---|---|---|
| 33.333K | 13 | 2 | 90 | 1 | False | 87.5% | 0.31% | 2200 m |
| 83.333K | 13 | 2 | 36 | 1 | False | 87.5% | 0.31% | 850 m |
| 125K | 13 | 2 | 24 | 1 | False | 87.5% | 0.31% | 550 m |
| 250K | 13 | 2 | 12 | 1 | False | 87.5% | 0.31% | 250 m |
| 500K | 13 | 2 | 6 | 1 | False | 87.5% | 0.31% | 100 m |
| 1M | 13 | 2 | 3 | 1 | False | 87.5% | 0.31% | 40 m |
For non-standard baud rates, an online calculator such as bittiming.can-wiki.info is helpful. The calculator needs the chip's CAN clock speed:
- SC20xxx — 48 MHz
- SC13xxx — 40 MHz
You can also read the clock speed at runtime via the SourceClock property on the CanController.

Sending and receiving messages
The example below sets up CAN at 1 megabaud, applies a few filters (covered in the next section), and sends a 6-byte message when the LDR button is pressed. Received messages are printed.
using System;
using System.Diagnostics;
using System.Threading;
using GHIElectronics.TinyCLR.Devices.Can;
using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Pins;
var ldrButton = GpioController.GetDefault().OpenPin(FEZBit.GpioPin.ButtonLeft);
ldrButton.SetDriveMode(GpioPinDriveMode.InputPullUp);
var can = CanController.FromName(FEZBit.CanBus.Can1);
// 1 Mbaud nominal bit timing.
can.SetNominalBitTiming(new CanBitTiming(
propagationPhase1: 13,
phase2: 2,
baudratePrescaler: 3,
synchronizationJumpWidth: 1,
useMultiBitSampling: false));
var message = new CanMessage(){
Data = new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2E, 0x20, 0x20 },
ArbitrationId = 0x11,
Length = 6,
RemoteTransmissionRequest = false,
ExtendedId = false,
FdCan = false,
BitRateSwitch = false,
};
// Accept arbitration IDs from 0x12 to 0x20 inclusive.
can.Filter.AddRangeFilter(Filter.IdType.Standard, 0x12, 0x20);
// Accept arbitration IDs from 0x500 to 0x1000 inclusive.
can.Filter.AddRangeFilter(Filter.IdType.Standard, 0x500, 0x1000);
// Accept arbitration IDs 0x11 and 0x13 (matched by mask 0xFD).
can.Filter.AddMaskFilter(Filter.IdType.Standard, 0x11, 0xFD);
// Accept arbitration ID 0x5678 only.
can.Filter.AddMaskFilter(Filter.IdType.Standard, 0x5678, 0xFFFF);
can.MessageReceived += Can_MessageReceived;
can.ErrorReceived += Can_ErrorReceived;
can.Enable();
while (true){
if (ldrButton.Read() == GpioPinValue.Low)
can.WriteMessage(message);
Thread.Sleep(100);
}
void Can_MessageReceived(CanController sender, MessageReceivedEventArgs e){
sender.ReadMessage(out var message);
Debug.WriteLine("Arbitration ID: 0x" + message.ArbitrationId.ToString("X8"));
Debug.WriteLine("Is extended ID: " + message.ExtendedId.ToString());
Debug.WriteLine("Is remote transmission request: " + message.RemoteTransmissionRequest.ToString());
Debug.WriteLine("Time stamp: " + message.Timestamp.ToString());
var data = "";
for (var i = 0; i < message.Length; i++) data += Convert.ToChar(message.Data[i]);
Debug.WriteLine("Data: " + data);
}
void Can_ErrorReceived(CanController sender, ErrorReceivedEventArgs e)
=> Debug.WriteLine("Error " + e.ToString());
Filtering
Filters let the CAN controller accept or ignore messages based on their arbitration ID — saving CPU time that would otherwise be spent in MessageReceived for messages your application doesn't care about.
Filter capacity — SC20xxx supports up to 64 standard IDs and 32 extended IDs per CAN channel. SC13xxx supports up to 14 filters.
Range filter
AddRangeFilter() accepts any message whose arbitration ID falls within a specified range, inclusive. You can add multiple range filters. The sample code above accepts IDs 0x12–0x20 and 0x500–0x1000.
Range filters are supported on SC20xxx only. SC13xxx does not have this filter type.
Mask filter
AddMaskFilter() accepts a message when (messageId & mask) == compare. Use it to match an individual ID, or a set of IDs that share certain bits.
In the sample code above, AddMaskFilter(IdType.Standard, 0x11, 0xFD) accepts IDs 0x11 and 0x13 — because masking those IDs with 0xFD yields 0x11 in both cases. The next call, AddMaskFilter(IdType.Standard, 0x5678, 0xFFFF), matches only ID 0x5678 since the mask covers all bits.
CAN-FD
CAN-FD (Flexible Data Rate) extends classic CAN with two features:
- Faster data-phase rate — the arbitration phase still runs at the slower nominal rate, but the data phase can switch to a much higher speed.
- Larger payloads — up to 64 bytes per message, versus 8 for classic CAN.
CAN-FD nodes are fully compatible with classic CAN nodes on the same bus — they coexist, with the FD nodes simply staying at the slower nominal rate when talking to classic nodes.
CAN-FD is supported on SC20xxx only. SC13xxx does not have CAN-FD.
To use CAN-FD, set up both bit timings, set FdCan = true and BitRateSwitch = true on the message, and you can carry up to 64 bytes of data per send.
// Slower arbitration-phase rate: 250 kbaud.
can.SetNominalBitTiming(new CanBitTiming(
propagationPhase1: 13,
phase2: 2,
baudratePrescaler: 12,
synchronizationJumpWidth: 1,
useMultiBitSampling: false));
// Faster data-phase rate: 1 Mbaud.
can.SetDataBitTiming(new CanBitTiming(
propagationPhase1: 13,
phase2: 2,
baudratePrescaler: 3,
synchronizationJumpWidth: 1,
useMultiBitSampling: false));
can.Enable();
var message = new CanMessage() {
Data = new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2E, 0x20, 0x20 },
ArbitrationId = 0x11,
Length = 6,
RemoteTransmissionRequest = false,
ExtendedId = false,
FdCan = true,
BitRateSwitch = true,
};
API reference
| Namespace | Description |
|---|---|
| GHIElectronics.TinyCLR.Devices.Can | CAN bus controller and message classes |