PolarFire® SoC Applications - CAN
Introduction
CAN (Controller Area Network) is an industrial network bus standard developed by Bosch® in the 1980s to connect various Electronic Control Units (ECUs) in vehicles without the need for bulky and complex wiring.
This article focuses on using the PolarFire® SoC's MSS CAN peripherals. It guides you through the required setup, programming the reference design, creating a bare-metal SoftConsole™ project, adding a simple CAN transmit and receive example, and verifying the output on the serial console. The article also covers the Yocto Project® workflow and demonstrates CAN use in embedded Linux®.
Prerequisites
Hardware Setup
- This application applies to the following PolarFire SoC-based boards:
- PolarFire SoC Icicle Kit User Guide (the board used in this article)
- PolarFire SoC - SoC Video Kit User Guide
- Three Micro-USB cables
- Logic analyzer (optional, for capturing CAN transmission signals)
- Linux host PC for the Yocto Project build environment
Software Setup
- SoftConsole™ Integrated Development Environment (IDE)
- Libero® SoC Design Suite 2024.2+
- Yocto Project host environment
- Logic analyzer software, if a logic analyzer is used
- MobaXTerm®, for interactive serial (UART) console access
Additional Resources
- PolarFire SoC Applications - MSS and Libero SoC Design Suite
- SoftConsole Integration Guide
- PolarFire SoC Icicle Kit Reference Design
- PolarFire SoC Video Kit Reference Design
Programming the Reference Design
To make the MSS CAN peripherals available to software at the application level, enable the CAN peripherals in the MSS configurator. In the reference design, the CAN0 and CAN1 peripherals are already enabled, and MSS CAN_0 and CAN_1 are routed to the MCP2562 Chip and available on the J25 and J27 headers with a standard CAN_H/CAN_L interface. Before writing software, it is important to understand where the CAN peripherals are connected on the target board.
The reference design also supports Yocto Linux, so the board-level CAN connection should be checked against the corresponding reference design documentation. For this article, the PolarFire Icicle Kit reference design is used.

| CAN 0 (CAN2 in Schematics) | CAN 1 (CAN1 in Schematics) | ||
|---|---|---|---|
| CAN High | J27 - 1 | CAN High | J25 - 1 |
| CAN Low | J27 - 2 | CAN Low | J25 - 2 |
| GND Pin | J27 - 3 | GND Pin | J25 - 3 |
Downloading the Reference Design
Open the latest release page for the PolarFire SoC Icicle Kit reference design:
Download the PolarFire SoC Icicle Kit reference design generation FlashPro images from the latest release.
Programming the FPGA Design
Open FlashPro Express.
Create a new project.
Select the reference design job file from the downloaded design package.
Use the job file matching the following pattern:
Click Run to program the Field-Programmable Gate Array (FPGA).
CAN in Linux® Environment
The CAN peripherals must be enabled and configured in Linux. That's why we need a Linux build system to configure it. For this article, we will use the Yocto Project.
Hart Software Services (HSS) Configurations
Hart Software Services (HSS) is the bootloader for PolarFire SoC. It runs first, sets up hardware, launches Linux or other apps, and is essential for multi-core and secure boot.
Objectives:
- Download and import HSS to SoftConsole
- Update references and build HSS
- Deploy HSS to PolarFire Icicle Kit
Download HSS.
Import the HSS project to SoftConsole by going to File > Import > Import Existing Project Into Workspace.

Browse the HSS folder and import the project into the workspace by clicking Finish.
Copy the MSS XML file into the HSS project.
Copy xml file to hart-software-services/boards/mpfs-icicle-kit/soc_fpga_design/xml/<your xml>.xml.
Copy and rename configurations for HSS.
Copy hart-software-services/boards/mpfs-icicle-kit-es/def_config to hart-software-services/.
Rename def_config to .config.
Edit .config file and update path to your xml by changing next line.
Build HSS and deploy.
Right-click on the project name.
Click on the build project.
Select PolarFire SoC program non-secure boot mode 1 run option and deploy project to SoC.
Yocto Project Configurations
In this section, we will create a Linux image and program it into the PolarFire SoC Icicle Kit.
Objectives:
- Setting up the Yocto Project building environment.
- Enabling the CAN peripherals and including the necessary packages in the build.
- Building a Linux image and deploying it into the SoC.
Creating Environment
To create the Linux build environment refer to the "OpenEmbedded/Yocto Project BSP layer for Microchip's SoCs" page.
Configuring CAN Peripheral
Prepare the Microchip Linux kernel (linux-mchp) source tree for local development:
Add the required libraries and applications to the image.
Open the conf/local.conf file and add the following variable at the end of the file:
CORE_IMAGE_EXTRA_INSTALL += "packagegroup-core-buildessential vim can-utils"
Save the file and exit.
Open the Linux kernel configuration menu and enable the CAN controller driver.
MACHINE=mpfs-icicle-kit bitbake linux-mchp -c menuconfig
In the configuration menu, navigate to Device Drivers.

Open the CAN Device Drivers menu:

Make sure that the Microchip PolarFire SoC CAN option is enabled:

Save the config by selecting Save > Exit.
Locate the board-specific DTS files and verify that the CAN peripherals are configured.
yocto-dev/build/workspace/sources/linux-mchp/arch/riscv/boot/dts/microchip/mpfs-icicle-kit-common.dtsi
Make sure that CAN0 and CAN1 are enabled in the DTS (in mpfs-icicle-kit-common.dtsi file):
2
3
4
5
6
7
8
status = "okay";
};
&can1 {
status = "okay";
};
Compile a customized Yocto Project Linux kernel recipe in a developer-friendly way, producing kernel binaries for PolarFire SoC.
Building Linux Image and Deploying
Execute the following command to build the Linux image:
MACHINE=mpfs-icicle-kit bitbake mchp-base-image
Here's the list of names of supporting machines:
| MACHINE | Board Name | Description |
|---|---|---|
| MACHINE=mpfs-icicle-kit | MPFS-ICICLE-KIT-ES, MPFS-ICICLE-KIT | PolarFire SoC Icicle Kit |
| MACHINE=mpfs-disco-kit | MPFS-DISCO-KIT | PolarFire SoC Discovery Kit |
| MACHINE=mpfs-video-kit | MPFS250-VIDEO-KIT | PolarFire SoC Video Kit |
As the build is done, you can locate your Linux image at:
yocto-dev/build/tmp-glibc/deploy/images/<board_name>/<image-name>.rootfs-***.wic
Follow the "Programming a Linux Image" instructions to deploy the built image to the eMMC/SD card memory.
After booting Linux on the PolarFire Icicle Board, log in as root and check if the MSS CANs devices appear:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP mode DEFAULT group default qlen 10
link/can
3: can1: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP mode DEFAULT group default qlen 10
link/can
4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:04:a3:59:d9:a8 brd ff:ff:ff:ff:ff:ff
5: end0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN mode DEFAULT group default qlen 1000
link/ether 00:04:a3:59:d9:a7 brd ff:ff:ff:ff:ff:ff
6: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/sit 0.0.0.0 brd 0.0.0.0
If no CAN interfaces are listed, that means the CAN devices are not enabled and you have to double-check the configuration, DTS, and driver modifications needed to be done.
Software Tools and Utilities
CAN-utils Command-Line Tools
Some of the most useful tools included in can-utils are:
- candump: Displays CAN frames received on one or more CAN interfaces
- cansend: Sends a single CAN or CAN FD frame
- cangen: Generates CAN traffic for testing and bus-load measurements
- cansniffer: Displays CAN traffic and highlights data bytes that change
Using CAN-utils Tools on PolarFire Icicle Kit:
The following examples show how to use these tools from the Linux userspace.
Before running the commands, make sure CAN1 (can0 in Linux) and CAN2 (can1 in Linux) are looped back.
To create the loopback connection, connect CAN1H to CAN2H and CAN1L to CAN2L.
Before using the CAN controllers, configure the can0 and can1 interfaces using standard ip commands with a bitrate of 500 kbit/s.
Configure can0:
# ip link set can0 type can bitrate 500000 restart-ms 100
# ip link set can0 up
Configure can1:
# ip link set can1 type can bitrate 500000 restart-ms 100
# ip link set can1 up
To check the configuration and status of can0:
2: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP mode DEFAULT group default qlen 10
link/can promiscuity 0 allmulti 0 minmtu 16 maxmtu 16
can state ERROR-WARNING (berr-counter tx 126 rx 0) restart-ms 100
bitrate 500000 sample-point 0.875
tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1 brp 1
mpfs_can: tseg1 2..16 tseg2 1..8 sjw 1..4 brp 0..32767 brp_inc 1
clock 8000000 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 tso_max_size 65536 tso_max_segs 65535 gro_max_size 65536 gso_ipv4_max_size 65536 gro_ipv4_max_size 65536 parentbus platform parentdev 2010c000.can
Send CAN frames through can0:
Monitor CAN frames received on both interfaces:
The command waits for CAN traffic and displays the interface name, CAN identifier, data length, and data bytes.
Generate continuous CAN traffic on can0:
Monitor changing CAN data bytes with cansniffer:
Application Programming in C Language
We can write a C program in userspace to interact with the CAN controllers. The example below performs a CAN loopback test between two CAN controllers.
Boot Linux on your PolarFire Icicle kit. Navigate to /media and create main.c using the vim editor:
Copy and paste the following C code to the main.c:
#include <linux/can/raw.h>
#include <net/if.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
static int can_open(const char *name)
{
int fd;
struct ifreq ifr = {0};
struct sockaddr_can addr = {0};
fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (fd < 0) {
perror("socket");
return -1;
}
strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
perror("SIOCGIFINDEX");
close(fd);
return -1;
}
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(fd);
return -1;
}
return fd;
}
static int can_test(int tx_fd, int rx_fd,
canid_t id, const unsigned char *data, int length)
{
struct can_frame tx = {0};
struct can_frame rx;
struct pollfd pfd = { .fd = rx_fd, .events = POLLIN };
tx.can_id = id;
tx.can_dlc = length;
memcpy(tx.data, data, length);
if (write(tx_fd, &tx, sizeof(tx)) != sizeof(tx)) {
perror("write");
return -1;
}
if (poll(&pfd, 1, 2000) <= 0) {
perror("CAN receive timeout");
return -1;
}
if (read(rx_fd, &rx, sizeof(rx)) != sizeof(rx)) {
perror("read");
return -1;
}
if ((rx.can_id & CAN_SFF_MASK) != id ||
rx.can_dlc != length ||
memcmp(rx.data, data, length) != 0) {
printf("Received frame does not match\n");
return -1;
}
printf("CAN ID 0x%03X received correctly\n", id);
return 0;
}
int main(void)
{
int can0 = can_open("can0");
int can1 = can_open("can1");
const unsigned char data0[] = {
0x11, 0x22, 0x33, 0x44
};
const unsigned char data1[] = {
0xAA, 0xBB, 0xCC, 0xDD
};
if (can0 < 0 || can1 < 0)
return 1;
printf("Testing can0 -> can1\n");
if (can_test(can0, can1, 0x123,
data0, sizeof(data0)) < 0)
goto fail;
printf("Testing can1 -> can0\n");
if (can_test(can1, can0, 0x321,
data1, sizeof(data1)) < 0)
goto fail;
printf("CAN loopback test PASSED\n");
close(can0);
close(can1);
return 0;
fail:
printf("CAN loopback test FAILED\n");
close(can0);
close(can1);
return 1;
}
After saving the modification, compile the C code on target:
Configure the CAN interfaces:
ip link set can0 type can bitrate 500000 restart-ms 100
ip link set can0 up
ip link set can1 down
ip link set can1 type can bitrate 500000 restart-ms 100
ip link set can1 up
Run the main executable:
CAN in Bare Metal Applications
Building and Programming the SoftConsole Project
To use the MSS CAN0 and CAN1 in a bare-metal application, we have to use SoftConsole to develop, build, and deploy the application.
For this application, let us take one of the GitHub bare-metal reference examples and change it so it uses the MSS CANs and communicates with each other.
Download the mpfs-blank-baremetal bare-metal application project from the mpfs-blank-baremetal repository and import it into the SoftConsole.
Replace the MSS Configuration XML file in the bare-metal project with the XML file used in your Libero SoC design suite project. The path to the file that SoftConsole will use to generate header files, which are then used by the MPFS HAL, is:
Replace the application source contents with the following code:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <string.h>
#include "mpfs_hal/mss_hal.h"
#include "inc/common.h"
#include "drivers/mss/mss_can/mss_can.h"
static uint64_t seconds_to_ticks(uint32_t seconds)
{
return (uint64_t)seconds * LIBERO_SETTING_MSS_RTC_TOGGLE_CLK;
}
void u54_1(void)
{
mss_can_msgobject tx_msg = {0};
mss_can_msgobject rx_msg = {0};
mss_can_filterobject rx_filter;
uint8_t counter = 0u;
char line[80];
(void)mss_config_clk_rst(MSS_PERIPH_CAN0, (uint8_t)MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_CAN1, (uint8_t)MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
(void)mss_config_clk_rst(MSS_PERIPH_MMUART2,(uint8_t)MPFS_HAL_FIRST_HART, PERIPHERAL_ON);
MSS_UART_init(&g_mss_uart2_lo, MSS_UART_115200_BAUD,
MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY | MSS_UART_ONE_STOP_BIT);
MSS_UART_polled_tx_string(&g_mss_uart2_lo,
(const uint8_t *)"\r\nCAN0 -> CAN1 transfer test\r\n");
MSS_CAN_init(&g_mss_can_0_lo, CAN_SPEED_8M_1M, (pmss_can_config_reg)0, 16u, 7u);
MSS_CAN_init(&g_mss_can_1_lo, CAN_SPEED_8M_1M, (pmss_can_config_reg)0, 16u, 7u);
rx_filter.ACR.L = 0x00000000u;
rx_filter.AMR.L = 0xFFFFFFFFu;
rx_filter.AMCR_D.MASK = 0xFFFFu;
rx_filter.AMCR_D.CODE = 0x0000u;
MSS_CAN_config_buffer(&g_mss_can_1_lo, &rx_filter);
MSS_CAN_set_mode(&g_mss_can_0_lo, CANOP_MODE_NORMAL);
MSS_CAN_set_mode(&g_mss_can_1_lo, CANOP_MODE_NORMAL);
MSS_CAN_start(&g_mss_can_0_lo);
MSS_CAN_start(&g_mss_can_1_lo);
tx_msg.ID = 0x100u;
tx_msg.IDE = 0u; /* standard 11-bit identifier */
tx_msg.ID = MSS_CAN_set_id(&tx_msg); /* left-align the ID for the hardware*/
tx_msg.L |= CAN_TX_WPNL_EBL; /* REQUIRED so TXREQ latches */
tx_msg.DLC = 8u;
while (1)
{
uint8_t rx_byte = 0u;
uint8_t got_rx = 0u;
uint64_t t_start;
tx_msg.DATALOW = (uint32_t)counter * 0x01010101u;
tx_msg.DATAHIGH = (uint32_t)counter * 0x01010101u;
(void)MSS_CAN_send_message(&g_mss_can_0_lo, &tx_msg);
t_start = readmtime();
while ((readmtime() - t_start) < seconds_to_ticks(1u))
{
if (MSS_CAN_get_message_av(&g_mss_can_1_lo) == CAN_VALID_MSG)
{
MSS_CAN_get_message(&g_mss_can_1_lo, &rx_msg);
rx_byte = (uint8_t)rx_msg.DATALOW;
got_rx = 1u;
}
}
if (got_rx)
{
sprintf(line, "transmitted %02X received %02X\r\n",
(unsigned)counter, (unsigned)rx_byte);
}
else
{
uint32_t estat = 0u;
(void)MSS_CAN_get_error_status(&g_mss_can_0_lo, &estat);
sprintf(line,
"transmitted %02X received -- (tx_err=%u rx_err=%u estat=0x%lx)\r\n",
(unsigned)counter,
(unsigned)MSS_CAN_get_tx_error_count(&g_mss_can_0_lo),
(unsigned)MSS_CAN_get_rx_error_count(&g_mss_can_1_lo),
(unsigned long)estat);
}
MSS_UART_polled_tx_string(&g_mss_uart2_lo, (const uint8_t *)line);
counter++;
}
}
Line 1–25: Header comment (what the test does + wiring)
Line 27–31: Includes
Line 33–37: seconds_to_ticks() timing helper
Line 39–45: u54_1() entry + local variables
Line 47–50: Enable clocks for CAN0, CAN1, MMUART2
Line 52–56: Init MMUART2 and print banner
Line 58–61: Init both CAN controllers at 1 Mbit/s
Line 63–68: CAN1 filter accepts all messages
Line 70–74: Set both controllers to normal mode and start
Line 76–89: Build TX frame (ID, WPNL key, DLC 8)
Line 91: Main loop
Line 97–100: Load counter into data bytes, transmit on CAN0
Line 102–112: Wait ~1 s, poll and read CAN1
Line 114–132: Format result or error counters
Line 133–135: Print on MMUART2, increment counter, repeat
Build the project and deploy it either in LIM for Debug mode or eNVM for Release mode.
Checking for the Results
Verify that the CAN output works correctly by opening the serial connection and observing the transmitted message.
Open MobaXTerm and set up the serial connections for the available FlashPro UART ports.
Monitor the UART terminal and observe the transaction status approximately every second.
Confirm that the expected output is displayed in the terminal.

Summary
This article introduced the MSS CAN peripheral on PolarFire SoC devices and showed how to use it in a bare-metal application. You reviewed the required hardware and software, programmed the PolarFire Icicle Kit reference design, created a SoftConsole project, enabled CAN support, added a simple CAN transmit application, and verified the output through a serial terminal.
The article also covered the Yocto Linux flow, configured the CAN interfaces using SocketCAN and can-utils, and demonstrated a user-space C loopback application.