stef
stef

Reputation: 807

Issue with SPI (Serial Port Comm), stuck on ioctl()

I'm trying to access a SPI sensor using the SPIDEV driver but my code gets stuck on IOCTL.

I'm running embedded Linux on the SAM9X5EK (mounting AT91SAM9G25). The device is connected to SPI0. I enabled CONFIG_SPI_SPIDEV and CONFIG_SPI_ATMEL in menuconfig and added the proper code to the BSP file:

static struct spi_board_info spidev_board_info[] {
    {
        .modalias = "spidev",
        .max_speed_hz = 1000000,
        .bus_num = 0,
        .chips_select = 0,
        .mode = SPI_MODE_3,
    },
    ...
};
spi_register_board_info(spidev_board_info, ARRAY_SIZE(spidev_board_info));

1MHz is the maximum accepted by the sensor, I tried 500kHz but I get an error during Linux boot (too slow apparently). .bus_num and .chips_select should correct (I also tried all other combinations). SPI_MODE_3 I checked the datasheet for it.

I get no error while booting and devices appear correctly as /dev/spidevX.X. I manage to open the file and obtain a valid file descriptor. I'm now trying to access the device with the following code (inspired by examples I found online).

#define MY_SPIDEV_DELAY_USECS 100
// #define MY_SPIDEV_SPEED_HZ 1000000
#define MY_SPIDEV_BITS_PER_WORD 8
int spidevReadRegister(int fd,
                       unsigned int num_out_bytes,
                       unsigned char *out_buffer,
                       unsigned int num_in_bytes,
                       unsigned char *in_buffer)
{
    struct spi_ioc_transfer mesg[2] = { {0}, };
    uint8_t num_tr = 0;
    int ret;

    // Write data
    mesg[0].tx_buf = (unsigned long)out_buffer;
    mesg[0].rx_buf = (unsigned long)NULL;
    mesg[0].len = num_out_bytes;
    // mesg[0].delay_usecs = MY_SPIDEV_DELAY_USECS,
    // mesg[0].speed_hz = MY_SPIDEV_SPEED_HZ;
    mesg[0].bits_per_word = MY_SPIDEV_BITS_PER_WORD;
    mesg[0].cs_change = 0;
    num_tr++;

    // Read data
    mesg[1].tx_buf = (unsigned long)NULL;
    mesg[1].rx_buf = (unsigned long)in_buffer;
    mesg[1].len = num_in_bytes;
    // mesg[1].delay_usecs = MY_SPIDEV_DELAY_USECS,
    // mesg[1].speed_hz = MY_SPIDEV_SPEED_HZ;
    mesg[1].bits_per_word = MY_SPIDEV_BITS_PER_WORD;
    mesg[1].cs_change = 1;
    num_tr++;

    // Do the actual transmission
    if(num_tr > 0)
    {
        ret = ioctl(fd, SPI_IOC_MESSAGE(num_tr), mesg);
        if(ret == -1)
        {
            printf("Error: %d\n", errno);
            return -1;
        }
    }

    return 0;
}

Then I'm using this function:

#define OPTICAL_SENSOR_ADDR "/dev/spidev0.0"

...

int fd;
fd = open(OPTICAL_SENSOR_ADDR, O_RDWR);
if (fd<=0) {
   printf("Device not found\n");
   exit(1);
}

uint8_t buffer1[1] = {0x3a};
uint8_t buffer2[1] = {0};
spidevReadRegister(fd, 1, buffer1, 1, buffer2);

When I run it, the code get stuck on IOCTL!

I did this way because, in order to read a register on the sensor, I need to send a byte with its address in it and then get the answer back without changing CS (however, when I tried using write() and read() functions, while learning, I got the same result, stuck on them). I'm aware that specifying .speed_hz causes a ENOPROTOOPT error on Atmel (I checked spidev.c) so I commented that part.

Why does it get stuck? I though it can be as the device is created but it actually doesn't "feel" any hardware. As I wasn't sure if hardware SPI0 corresponded to bus_num 0 or 1, I tried both, but still no success (btw, which one is it?).

UPDATE: I managed to have the SPI working! Half of it.. MOSI is transmitting the right data, but CLK doesn't start... any idea?

Upvotes: 5

Views: 11786

Answers (2)

Christian Rapp
Christian Rapp

Reputation: 1903

I think there are several issues here. First of all SPI is bidirectional. So if yo want to send something over the bus you also get something. Therefor always you have to provide a valid buffer to rx_buf and tx_buf.

Second, all members of the struct spi_ioc_transfer have to be initialized with a valid value. Otherwise they just point to some memory address and the underlying process is accessing arbitrary data, thus leading to unknown behavior.

Third, why do you use a for loop with ioctl? You already tell ioctl you haven an array of spi_ioc_transfer structs. So all defined transaction will be performed with one ioctl call.

Fourth ioctl needs a pointer to your struct array. So ioctl should look like this:

 ret = ioctl(fd, SPI_IOC_MESSAGE(num_tr), &mesg);

You see there is room for improvement in your code.

This is how I do it in a c++ library for the raspberry pi. The whole library will soon be on github. I'll update my answer when it is done.

void SPIBus::spiReadWrite(std::vector<std::vector<uint8_t> > &data, uint32_t speed,
                          uint16_t delay, uint8_t bitsPerWord, uint8_t cs_change)
{
    struct spi_ioc_transfer transfer[data.size()];
    int i = 0;
    for (std::vector<uint8_t> &d : data)
    {
        //see <linux/spi/spidev.h> for details!
        transfer[i].tx_buf = reinterpret_cast<__u64>(d.data());
        transfer[i].rx_buf = reinterpret_cast<__u64>(d.data());
        transfer[i].len = d.size(); //number of bytes in vector
        transfer[i].speed_hz = speed;
        transfer[i].delay_usecs = delay;
        transfer[i].bits_per_word = bitsPerWord;
        transfer[i].cs_change = cs_change;
        i++
    }
    int status = ioctl(this->fileDescriptor, SPI_IOC_MESSAGE(data.size()), &transfer);
    if (status < 0)
    {
        std::string errMessage(strerror(errno));
        throw std::runtime_error("Failed to do full duplex read/write operation "
                                 "on SPI Bus " + this->deviceNode + ". Error message: " +
                                 errMessage);
    }
}

Upvotes: 2

stdcall
stdcall

Reputation: 28900

When I'm working with SPI I always use an oscyloscope to see the output of the io's. If you have a 4 channel scope ypu can easily debug the issue, and find out if you're axcessing the right io's, using the right speed, etc. I usually compare the signal I get to the datasheet diagram.

Upvotes: 3

Related Questions