Draft — ready to post on Nordic DevZone / Zephyr mailing-list
Title
nRF5340 DK + MCP2515/TJA1050 module — can_start() always returns -EIO even in loop-back
Hardware
Item | Details |
---|---|
MCU board | nRF5340 DK (core cpuapp) |
CAN module | “MCP2515 CAN-BUS Module” (blue/green PCB) with on-board TJA1050 transceiver |
SPI wiring | SCK P0.04, MOSI P0.05, MISO P0.06, CS P1.09 |
INT | P1.15 — declared GPIO_ACTIVE_LOW |
Supply | VCC = 5 V, |
MCP2515 crystal | 8 MHz (confirmed on OSC2 pin with scope) |
Software
-
Zephyr 4.1 (commit 82b802f9ab4e)
-
prj.conf
CONFIG_GPIO=yCONFIG_PINCTRL=yCONFIG_SPI=y
CONFIG_CAN=yCONFIG_CAN_MCP2515=y
CONFIG_LOG=yCONFIG_CAN_MCP2515_LOG_LEVEL_DBG=y
CONFIG_PRINTK=yCONFIG_MAIN_STACK_SIZE=2048 -
Devicetree overlay (extract)
&spi3 {status = "okay";pinctrl-0 = <&spi3_default>;pinctrl-1 = <&spi3_sleep>;pinctrl-names = "default", "sleep";cs-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
mcp2515: can0@0 { /* label: mcp2515 unit-addr: 0 */compatible = "microchip,mcp2515";reg = <0>;spi-max-frequency = <1000000>;osc-freq = <8000000>;int-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;bus-speed = <250000>;status = "okay";label = "CAN_0";};}; /* <-- et là aussi : }; */
/* On coupe les SPIM pour éviter l’avertissement pinctrl */&spi0 { status = "disabled"; };&spi1 { status = "disabled"; };&spi2 { status = "disabled"; };&spi4 { status = "disabled"; };main.c:
/* TX – src/main.c */#include <zephyr/kernel.h>#include <zephyr/drivers/gpio.h>#include <zephyr/drivers/can.h>#include <zephyr/sys/printk.h>#include <zephyr/drivers/spi.h>/* TX – test loop-back interne MCP2515* Zephyr 4.1 – nRF5340 DK (core app)*/
/* --------- LED alias --------- */#define LED0_NODE DT_ALIAS(led0)static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
/* --------- Contrôleur CAN --------- */static const struct device *const can_dev = DEVICE_DT_GET(DT_NODELABEL(mcp2515));static const struct device *const spi3_dev = DEVICE_DT_GET(DT_NODELABEL(spi3));/* File de réception – 5 messages max, alignement 4 B */K_MSGQ_DEFINE(rx_msgq, sizeof(struct can_frame), 5, 4);
void main(void){/* 1) Vérification des périphériques */if (!device_is_ready(led.port) || !device_is_ready(can_dev) || !device_is_ready(spi3_dev)) {printk("priphes pas pret\n");return;}gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
/* ---------- test SPI brut -------------------------------------- */uint8_t tx_buf[3] = { 0x03, 0x0E, 0x00 }; /* READ + addr + dummy */uint8_t rx_buf[3];const struct spi_buf tx = { .buf = tx_buf, .len = 3 };const struct spi_buf rx = { .buf = rx_buf, .len = 3 };const struct spi_buf_set tx_set = { .buffers = &tx, .count = 1 };const struct spi_buf_set rx_set = { .buffers = &rx, .count = 1 };
static const struct spi_cs_control spi3_cs = { .gpio = GPIO_DT_SPEC_GET(DT_NODELABEL(spi3), cs_gpios), .delay = 0 };static const struct spi_config spi_cfg = {.frequency = 1000000,.operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB,.slave = 0,.cs = spi3_cs,};
int spi_err = spi_transceive(spi3_dev, &spi_cfg, &tx_set, &rx_set);printk("SPI test -> %d, status byte = 0x%02X, CANSTAT = 0x%02X \n", spi_err, rx_buf[1], rx_buf[2]);
/* 2) Mode LOOPBACK **avant** can_start() */can_set_mode(can_dev, CAN_MODE_LOOPBACK);int ret = can_start(can_dev);printk("can_start ret = %d\n", ret);if (ret) return;
/* 4) Filtre pour récupérer nos propres trames */const struct can_filter filter = {.id = 0x123,.mask = CAN_STD_ID_MASK,.flags = 0 /* trame standard */};can_add_rx_filter_msgq(can_dev, &rx_msgq, &filter);
printk("== LOOPBACK actif – envoi toutes les 100 ms ==\n");
/* Trame à émettre (réutilisée à chaque tour) */struct can_frame frame = {.id = 0x123,.flags = 0, /* standard */.dlc = 2,.data = { 0xAA, 0x55 },};
/* 5) Boucle principale */while (1) {/* Envoi non bloquant : retourne 0 ou -EAGAIN si buffers pleins */int err = can_send(can_dev, &frame, K_NO_WAIT, NULL, NULL);printk("can_send -> %d\n", err);
/* Lecture (non bloquante) de la trame renvoyée par loop-back */struct can_frame rx;if (k_msgq_get(&rx_msgq, &rx, K_MSEC(10)) == 0) {printk("Reçue : %02X %02X\n", rx.data[0], rx.data[1]);}
gpio_pin_toggle_dt(&led); /* témoin visuel */k_msleep(100);}}
The application sets loop-back mode right after can_start()
.
What I see on the console
*** Booting Zephyr OS build v4.1.0-4196-g82b802f9ab4e ***
SPI test -> 0, CANSTAT = 0x00
can_start ret = -5 /* -EIO */
-
A manual
spi_transceive()
right beforecan_start()
succeeds (err = 0
)
but always reads 0x00 from CANSTAT. -
Therefore Zephyr’s driver (
can_mcp2515
) aborts and returns -EIO.
What I have already checked
-
Power rails
-
VCC on module = 5.08 V
-
3V3 regulator output = 3.30 V
-
MCP2515 VDD (pin 3) = 5.08 V
- sheifl level between GPIO nrf5340 and MCP2515
-
-
Oscillator
-
Stable 8 MHz square wave on MCP2515 OSC2 (pin 10)
-
-
RESET̅ (pin 20) is high (≈ 5 V)
-
CS toggles low during every SPI frame (scope)
-
INT , stays high — I never see a low pulse
-
osc-freq
set to 8 MHz, re-built with--pristine
-
Re-tested with a second brand-new module — same result.
Question(s)
-
Under Zephyr, does the MCP2515 driver rely on INT going low before it
accepts that the device has reached “Configuration” mode?
I never see INT toggle. -
Are there additional registers (CLKOUT, CNF1-3, …) that must be written
manually when using an 8 MHz crystal?
All examples I found assume 16 MHz. -
Any other reason why the driver would keep reading CANSTAT = 0×00 while
the oscillator is running and RESET̅ is high?
I’ve been stuck for several days — any hint or experience with 8 MHz
MCP2515 modules on nRF5340 would be greatly appreciated!