This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

TCAN4550 chip select, EasyDMA, 8 vs 32 bit SPI words and nRF52832

Hello! I have a custom board with two TCAN4550 over SPI to an nRF52832. One is for vehicle communication, the other for our proprietary bus communicating with external units. I've ported the official Texas TCAN library to Zephyr and would be more than happy to share it once it's been cleaned up.

The official driver uses three functions (start / burst / end). Start flips chip select low and transmits the header (first 8 bits are either a read or write cmd, next 16 bits are reg address and last 8 bits are number of words for the following read/write). Burst transmits/receives the number of words specified in the header. End just flips chip select high. Each word needs to be 32 bits, and each transaction can contain up to 256 words plus the header.

For a long time, I struggled to achieve communication, then discovered Zephyr pulls the chip select high after each individual spi_write / spi_read / spi_transceive. This reset the shift register in TCAN between start and burst functions. The TCAN requires chip select to be held low during the whole transaction. I disabled the zephyr chip select (basically sat it to an unused pin at the moment), and explicitly set the chip select before and after each complete spi transaction. Now everything is working.

My question is, what is the most appropriate way of controlling the chip select for the TCAN in Zephyr, when having two instances of the same driver and knowing it shall keep low until transaction is complete? And for safety, nothing else shall control the chip select except the driver. I could of course re-write the driver to concatenate the whole message, before sending it in a dynamically sized array. But I suspect unpredictable behaviour. They will be running in two individual threads.

Second question is, what is the most effective way of communicating? Four 8 bit words at a time or one 32 bit word (I didn't find any documentation if 32 is even supported)?

The nRF52832 will also have quite a lot of BT traffic simultaneous with TCAN. Would using EasyDMA be offloading the nRF significantly, and are there any drawbacks?

Parents
  • Hi,

     

    If you choose to control your CSN pins manually in the application, that is fine. If you were to write a zephyr driver for your devices, you could have done it like this:

    https://github.com/nrfconnect/sdk-zephyr/blob/master/boards/arm/ruuvi_ruuvitag/ruuvi_ruuvitag.dts#L75-L97

    Or: use two SPI instances (ie: SPI0 and SPI1) to poll them simultaneously if you have enough GPIOs available. 

    My question is, what is the most appropriate way of controlling the chip select for the TCAN in Zephyr, when having two instances of the same driver and knowing it shall keep low until transaction is complete? And for safety, nothing else shall control the chip select except the driver. I could of course re-write the driver to concatenate the whole message, before sending it in a dynamically sized array. But I suspect unpredictable behaviour. They will be running in two individual threads.

    You can use a mutex to ensure that the threads don't overlap:  https://docs.zephyrproject.org/latest/reference/kernel/synchronization/mutexes.html

     

    Second question is, what is the most effective way of communicating? Four 8 bit words at a time or one 32 bit word (I didn't find any documentation if 32 is even supported)?

     One transaction is more effective than 4 individual, but it all boils down to how the external device behaves as well.

    To use EasyDMA, you can change this line:

    &spix {
        compatible = "nordic,nrf-spi";
        ...
    };

     

    To:

    &spix {
        compatible = "nordic,nrf-spim";
        ...
    };

     

    Kind regards,

    Håkon

  • If you choose to control your CSN pins manually in the application, that is fine. If you were to write a zephyr driver for your devices, you could have done it like this:

    This is what I have done already. Two instances of the same driver, with different chip selects. But the problem is that zephyr controls the chip select independently (before and after each individual spi_write/read command, and doesn't hold it over multiple calls to the spi_write/read functions). I guess controlling it in the application is the only way then?

    One transaction is more effective than 4 individual, but it all boils down to how the external device behaves as well.

    I tried this:

    spi_cfg.operation = (SPI_WORD_SET(32) | SPI_TRANSFER_MSB)

    with this code to test 32 bit words:

    #include <zephyr.h>
    #include <sys/printk.h>
    #include <drivers/spi.h>
    #include <nrfx.h>
    #include <nrfx_uarte.h>
    
    
      struct spi_cs_control spi_cs = {
        .gpio_dev = NULL,
        .gpio_pin = 8,
        .gpio_dt_flags = GPIO_ACTIVE_LOW,
        .delay = 0,
      };
    
      static const struct spi_config spi_cfg = {
        .operation = (SPI_WORD_SET(32) | SPI_TRANSFER_MSB),
        .frequency = 10000000,
        .slave = 0,
        .cs = &spi_cs,
      };
    
    
    struct device *spi_dev;
    
    static void spi_init(void) {
    
      const char* const spiName = "SPI_1";
    
      spi_dev = device_get_binding(spiName);
    
      if (spi_dev == NULL) {
        printk("Could not get %s device\n", spiName);
        return;
      }
    
      spi_cs.gpio_dev = device_get_binding("GPIO_0");
    
    }
    
    void spi_test_send(void)
    {
      int err;
      static uint32_t tx_buffer[1];
      static uint32_t rx_buffer[1];
    
      const struct spi_buf tx_buf = {
        .buf = tx_buffer,
        .len = sizeof(tx_buffer)
      };
      const struct spi_buf_set tx = {
        .buffers = &tx_buf,
        .count = 1
      };
    
      struct spi_buf rx_buf = {
        .buf = rx_buffer,
        .len = sizeof(rx_buffer),
      };
      const struct spi_buf_set rx = {
        .buffers = &rx_buf,
        .count = 1
      };
    
      err = spi_transceive(spi_dev, &spi_cfg, &tx, &rx);
      if (err) {
        printk("SPI error: %d\n", err);
      } else {
        printk("TX sent: %x\n", tx_buffer[0]);
        printk("RX recv: %x\n", rx_buffer[0]);
        tx_buffer[0]++;
      }
    }
    
    
    void main(void) {
    
      printk("SPIM Example\n");
      printk("Compiled: %s %s\n", __DATE__, __TIME__);
    
      spi_init();
    
      while (1) {
        spi_test_send();
    	k_usleep(1000000);
    
      }
    }

    But spi_transceive() gives me "SPI error: -22" and logger prints "spi_nrfx_spi: Word sizes other than 8 bits are not supported". How do I setup for 32 bit SPI words?

    I tried EasyDMA:

    DTS: compatible = "nordic,nrf-spim";

    and proj.conf

    CONFIG_GPIO=y
    CONFIG_SERIAL=y
    CONFIG_UART_INTERRUPT_DRIVEN=y
    
    #Debug Specific
    CONFIG_LOG=y
    CONFIG_LOG_DEFAULT_LEVEL=1
    CONFIG_LOG_MAX_LEVEL=1
    
    # SPI
    CONFIG_SPI=y
    CONFIG_SPI_NRFX=y
    CONFIG_NRFX_SPIM1=y
    CONFIG_MAIN_STACK_SIZE=4096

    But the MCU freezes (used the same code from above, but with 8 bit words):

    *** Booting Zephyr OS build zephyr-v2.5.0-352-gd96c517bab0d  ***
    SPIM Example
    Compiled: Apr 28 2021 13:09:04
    Could not get SPI_1 device
    [00:00:00.276,733] .[1;31m<err> os: ***** MPU FAULT *****.[0m
    [00:00:00.276,733] .[1;31m<err> os:   Instruction Access Violation.[0m
    [00:00:00.276,733] .[1;31m<err> os: r0/a1:  0x00000000  r1/a2:  0x00005fa8  r2/a3:  0x20001c88.[0m
    [00:00:00.276,763] .[1;31m<err> os: r3/a4:  0x20001c98 r12/ip:  0x0000000a r14/lr:  0x00000415.[0m
    [00:00:00.276,763] .[1;31m<err> os:  xpsr:  0x01000000.[0m
    [00:00:00.276,763] .[1;31m<err> os: Faulting instruction address (r15/pc): 0xfcf7fcb4.[0m
    [00:00:00.276,763] .[1;31m<err> os: >>> ZEPHYR FATAL ERROR 0: CPU exception on CPU 0.[0m
    [00:00:00.276,763] .[1;31m<err> os: Current thread: 0x20000210 (unknown).[0m
    [00:00:01.216,003] .[1;31m<err> os: Halting system.[0m

    What do I do wrong?

  • Hi,

     

    My apologies, I should have been clearer. I thought you asked if you should send byte-by-byte for each transaction or send 4 bytes in one transaction.

    The SPI is byte aligned, but the transfer using DMA can be up to 255 bytes in one transaction.

     

    Meaning: it does not support setting 32 bit in the zephyr SPI API.

     

    okwestern said:
    I guess controlling it in the application is the only way then?

    Yes, either run them synchronously to ensure that they do not overlap, or use a mutex to ensure the same thing. 

     

    okwestern said:

    I tried EasyDMA:

    DTS: compatible = "nordic,nrf-spim";

    and proj.conf

     You're using SPI1, which needs to be explicitly enabled:

    CONFIG_SPI_1=y
     

    Note: You're getting a MPU fault because the sample isn't handling the spi_dev=NULL scenario.

     

    Kind regards,

    Håkon

  • With 

    compatible = "nordic,nrf-spim";

    and

    CONFIG_SPI=y
    CONFIG_SPI_1=y
    CONFIG_SPI_NRFX=y
    CONFIG_NRFX_SPIM1=y

    Why is this one failing?

    spi_dev = device_get_binding("SPI_1");

  • Hi,

     

    My apologies. Since you are using nrf52832, you are running into this errata when enabling the SPIM peripheral:

    https://infocenter.nordicsemi.com/topic/errata_nRF52832_Rev2/ERR/nRF52832/Rev2/latest/anomaly_832_58.html?cp=4_2_1_0_1_8

     

    You can override this by adding this configuration:

    https://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_SOC_NRF52832_ALLOW_SPIM_DESPITE_PAN_58.html

     

    But, please read the description carefully. If you read only one byte (and transfer 1 or 0 bytes), you will be affected by this erratum, unfortunately.

     

    Kind regards,

    Håkon

Reply Children
No Data
Related