1
votes

I have the following weird problem. I have setup the BBB to activate the spi1 module. The the module is connected to an F-RAM chip (FM25CL64B). I have done all the necessary configuration. The /dev/spidev1.0 is present and I wrote a small program to write to, and read from the chip, by opening the /dev/spidev1.0 and using ioctl with the SPI_IOC_MESSAGE macro command.

Using that program I managed to successfully write 32 bytes of text in to the F-RAM chip. Reading also seemed to be successful... How do I know they both were successful? I used a logic analyzer with an SPI decoder activated to actually see what's going on over all four of the SPI lines. Monitoring all SPI lines I could see that the write and read operations generate the correct signals with the correct timings, and all signals are in sync. The CS enables the chip during the transaction, CLK clocks every byte in 8-bit words (as configured), the data lines show the correct values, which I could see thanks to the SPI decoder which shows the byte value right above each 8-bit signal sequence of both MOSI and MISO lines.

The problem is that even though I can see that correct information is sent over the MISO line during read operation, the buffer I provide to the ioctl(iSPIR, SPI_IOC_MESSAGE(2), xfer) is filled with zeros.

I intentionally initialized that buffer with other values, so I can see if the ioctl even writes into it. And it does. Zeros.

Now, the fact that I could see all the bytes sent over the MISO line during the read operation, proves that the writing operation not only looked right in the analyzer but actually wrote the intended data during the previous write operation.

I checked several times whether the MISO line is configured correctly, in the dts file (which I can rebuild and reinstall on demand). I checked if it's the correct pin, and if it's configured as input. Everything seemed to be correctly configured.

I ran the program as root in case there are permission issues - no difference.

I also implemented the spi communication in GPIO mode. I.e. the spi module is disabled, all lines configured as GPIO. CE, CLK, MOSI configured as otuputs and the MISO configured as input. This way I could implement the entire communication in software so I could have full control over the lines. Doing that, this time, I was able to successfully fill a buffer with the correct data from the F-RAM chip. I.e. the sequential read operation went fine from the F-RAM chip all the way to my user space buffer. I was able to print the data into the console. However, that worked way too slow. Also I find it inefficient to use purely software implementation of SPI com when there is a module available for use.

In order to write my sample program I used the spi_test.c open source example available online. I also built and ran spi_test.c itself with no modification, same result.

Here is listing of my program (Relevant snippets):

// SPI config ...

int InitSPIReadMode(const char* pstrDeviceF)
{
        int file;
        __u8  wr_mode = SPI_MODE_0, rd_mode = SPI_MODE_0, lsb = 0, bits = 8;
        __u32 speed = CLOCK_FREQ_HZ; // 500kHz

        if((file = open(pstrDeviceF, O_RDWR)) < 0)
        {
                printf("Failed to open the bus.");
                /* ERROR HANDLING; you can check errno to see what went wrong */
                exit(1);
        }
        if(ioctl(file, SPI_IOC_RD_MODE, &rd_mode) < 0)
        {
                printf("SPI rd_mode\n");
                return -1;
        }
        if(ioctl(file, SPI_IOC_RD_LSB_FIRST, &lsb) < 0)
        {
                printf("SPI rd_lsb_fist\n");
                return -1;
        }
        if(ioctl(file, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0)
        {
                printf("SPI bits_per_word\n");
                return -1;
        }
        if(ioctl(file, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0)
        {
                printf("SPI max_speed_hz\n");
                return -1;
        }
        printf("%s: spi wr-mode=%d, spi rd-mode=%d, %d bits per word, %s, %d Hz max\n", pstrDeviceF, wr_mode, rd_mode, bits, lsb ? "(lsb first) " : "(msb first)", speed);

        xfer[0].cs_change = 0; /* Keep CS activated */
        xfer[0].delay_usecs = 0; //delay in us
        xfer[0].speed_hz = CLOCK_FREQ_HZ; //speed
        xfer[0].bits_per_word = 8; // bites per word 8

        xfer[1].cs_change = 0; /* Keep CS activated */
        xfer[1].delay_usecs = 0;
        xfer[1].speed_hz = CLOCK_FREQ_HZ;
        xfer[1].bits_per_word = 8;

        return file;
}

In the main function: (as the logic analyzer shows this code correctly sends the command, address and clocks the 32 bytes of data afterwards)


                int iSPIR = InitSPIReadMode("/dev/spidev1.0"); //open("/dev/spidev1.0", O_RDWR | O_SYNC);

                char arrInstruct[3] = { OPCO_READ, 0x00, 0x00 };
                char arrFRamData[512];

                for(int pos = 0; pos < 512; pos++) arrFRamData[pos] = pos;

                xfer[0].tx_buf = (unsigned long)arrInstruct;
                xfer[0].len = 3;

                xfer[1].rx_buf = (unsigned long)arrFRamData;
                xfer[1].len = 32;

                if(ioctl(iSPIR, SPI_IOC_MESSAGE(2), xfer) < 0) printf("ioctl write error %s.\n", strerror(errno));

// hex dumping of the arrFRamData buffer.

xfer is a global variable defined as:

struct spi_ioc_transfer xfer[2];

Thanks a lot in advance! :)

1
"CLK clocks every byte in 8-bit words (as configured)" It's not as simple as that. Apart from baudrate, SPI has two other clock settings: clock polarity (usually called CPOL) and clock phase (usually called CPHA). These must be as required by the slave, otherwise you will get "clock skew", with the clock running half a bit off, which in turn means that the SPI will work most of the time but occasionally you get strange intermittent errors. This is a very common problem and not easy to spot on a logic analyser.Lundin
Sorry for not mentioning that. CPHA and CPOL are also correctly configured. As you can see the code in InitSPIReadMode, there I set rd_mode = SPI_MODE_0. That takes care of CPHA and CPOL. The chip itself can work in mode 0 and mode 3, automatically without the need of configuration. After all If they weren't I wouldn't have been able to write data into the chip and then read it back.Ivan Ivanov
Suggest: xfer[0].tx_buf = (uintptr_t)arrInstruct; and similar for rx_buf - but again, not the cause of your problem - just best practice.Clifford
You have a comment // hex dumping of the arrFRamData buffer., if that is the only way you are observing that the data is all-zero, you are asking us to trust that that your debug code is correct. It may well be, but you should include it in case the problem is flawed inspection rather then a real error. Or better, why not simply inspect the buffer in your debugger, and copy & paste the debugger memory dump?Clifford
You ought to explicitly set xfer[0].rx_buf and xfer[1].tx_buf to (uintptr_t)NULL for half duplex operation. They may well already be set, but xfer is (ill-advisedly) global, so that may not always be true - you don't want the remnants of some earlier operation in those members. Are you sure the "read-back" data you are observing on the logic analyser is not just the SPI clocking out the data from the previous write operation during the read? That is to say is it on the MOSI or the MISO?Clifford

1 Answers

1
votes

I found what the problem was with my setup. But even though the whole thing works now, the solution I applied raises more questions than answers.

So I found this solution in an article (https://elinux.org/BeagleBone_Black_Enable_SPIDEV) on the beaglebone wiki.

There I noticed that, in the device-tree overlay they set the CLK as an input. Reading the entire article turned out nothing as to why the CLOCK on the BBBs side has to be an input. Even though it's the master... The article only explains how to build and install the new DTBO in order to activate the SPI1 module.

So, even though it didn't make any sense to me, I though it wouldn't hurt to try changing the CLK line in my DTBO file form output to input to see what happens... And it worked! :O

Now, why is it so strange that the setup works like this. The BBB is supposed to be the SPI master, so its clock line should be an Output in order to drive this synchronous communication. That means the FM25CL64B chip must act as SPI slave. I just checked the datasheet and yes, the CLK on the chips side, is an input. So the CLK on the BBB must be an output. That's how I've configured the CLK pin on the BBB. As an output. And it didn't work.

This is why I'm so puzzled. So it works even though both ends of the CLK line are inputs?! Looking at the logic analyzers output, I can clearly see that the CLK line is being driven correctly. But if both the master an slave have inputs on that line there shouldn't be anything to generate those impulses?! There is nothing else connected to that line....