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

stef picture stef · May 31, 2012 · Viewed 9.7k times · Source

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?

Answer

stdcall picture stdcall · May 31, 2012

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.