nRF5340 DK + MCP2515/TJA1050 module — can_start() always returns -EIO even in loop-back

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=y
    CONFIG_PINCTRL=y
    CONFIG_SPI=y

    CONFIG_CAN=y            
    CONFIG_CAN_MCP2515=y  

    CONFIG_LOG=y
    CONFIG_CAN_MCP2515_LOG_LEVEL_DBG=y


    CONFIG_PRINTK=y
    CONFIG_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 before can_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

  1. 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
  2. Oscillator

    • Stable 8 MHz square wave on MCP2515 OSC2 (pin 10)

  3. RESET̅ (pin 20) is high (≈ 5 V)

  4. CS toggles low during every SPI frame (scope)

  5. INT , stays high  — I never see a low pulse

  6. osc-freq set to 8 MHz, re-built with --pristine

  7. 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!

Related