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!

Parents
  • I do not think Zephyr’s MCP2515 driver waits on the INT pin at reset. It polls the CANSTAT register, and because your 8 MHz crystal and board combination (and/or timing-table support in the driver) isn’t matching one of the driver’s built-in oscillator-to-CNF entries, every read of CANSTAT comes back 0x00.

    I would try these first

    • Ensure proper 3.3 V power and level shifting so the MCP2515 actually sees your SPI RESET/READ commands.

    • Add or patch in the correct CNF1-3 timing values for 8 MHz @ 250 kbps into the Zephyr driver (or switch to a 16 MHz crystal).

    • Rebuild with the updated driver; you should then see CANSTAT become 0x04 (Configuration mode) and can_start() return 0.

    If that doesn't help, then you should seek more help in Zephyr forums as we are not experts in the drivers for these external sensors. We just make sure that if those sensors are having standard transport layers, then we do not fail to communicate with them. Since this post and this post shows that developers succeeded to communicate with nRF53/nRF52 and this sensor, our hardware works and the Zephyr driver might have some issue. Not saying that this is something Nordic shouldn't help you with, but we have some engineers helping in Zephyr forums as well who should be more equipped to help with this if they used this sensor.

Reply
  • I do not think Zephyr’s MCP2515 driver waits on the INT pin at reset. It polls the CANSTAT register, and because your 8 MHz crystal and board combination (and/or timing-table support in the driver) isn’t matching one of the driver’s built-in oscillator-to-CNF entries, every read of CANSTAT comes back 0x00.

    I would try these first

    • Ensure proper 3.3 V power and level shifting so the MCP2515 actually sees your SPI RESET/READ commands.

    • Add or patch in the correct CNF1-3 timing values for 8 MHz @ 250 kbps into the Zephyr driver (or switch to a 16 MHz crystal).

    • Rebuild with the updated driver; you should then see CANSTAT become 0x04 (Configuration mode) and can_start() return 0.

    If that doesn't help, then you should seek more help in Zephyr forums as we are not experts in the drivers for these external sensors. We just make sure that if those sensors are having standard transport layers, then we do not fail to communicate with them. Since this post and this post shows that developers succeeded to communicate with nRF53/nRF52 and this sensor, our hardware works and the Zephyr driver might have some issue. Not saying that this is something Nordic shouldn't help you with, but we have some engineers helping in Zephyr forums as well who should be more equipped to help with this if they used this sensor.

Children
No Data
Related