Ioctl() and spidev

Hello to all,

after opening spidev I use ioctl() to set some parameters:

int spi_open(char *name, uint32_t mode, uint8_t bits, uint32_t speed)
{
    int fd;
    int err;

    fd=open(name, O_RDWR | O_NONBLOCK);
    if(fd < 0)
        return -1;

    err=ioctl(fd, SPI_IOC_RD_MODE32, &mode);
    if(!err)
        err=ioctl(fd, SPI_IOC_WR_MODE32, &mode);
    if(!err)
        err=ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
    if(!err)
        err=ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    if(!err)
        err=ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
    if(!err)
        err=ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

    if(!err)
        return fd;
    close(fd);
    return -1;
}

I set the bits-per-word to 32 and max-speed to 8312500.
When I then send something without explicitly setting bits_per_word and speed_hz in the transfer-buffer, like this:

ssize_t spi_transfer(int fd, void *out, void *in, size_t len)
{
    struct spi_ioc_transfer buff;

    memset(&buff, 0, sizeof(buff));
    buff.tx_buf=(__u64)out;
    buff.rx_buf=(__u64)in;
    buff.len=len;
    buff.delay_usecs=0;
    if(ioctl(fd, SPI_IOC_MESSAGE(1), &buff) < 0)
        return -1;
    return len;
}

the data is been send with a clock-frequency of 20MHz and 8Bit per word.

/usr/include/linux/spi/spidev.h reads:

/**
 * struct spi_ioc_transfer - describes a single SPI transfer
 * @tx_buf: Holds pointer to userspace buffer with transmit data, or null.
 *  If no data is provided, zeroes are shifted out.
 * @rx_buf: Holds pointer to userspace buffer for receive data, or null.
 * @len: Length of tx and rx buffers, in bytes.
 * @speed_hz: Temporary override of the device's bitrate.
 * @bits_per_word: Temporary override of the device's wordsize.
 * @delay_usecs: If nonzero, how long to delay after the last bit transfer
 *  before optionally deselecting the device before the next transfer.
 * @cs_change: True to deselect device before starting the next transfer.
 *

For me the word temporary means: then I do not set anything else (initialized with 0) for this buffer, the parameters I set with the ioctl()s after opening the device are used.

Is there anything wrong in my understanding?

Why do I get a clock-rate of 20MHz when I set the maximum to 8MHz?

Thanks

Hi @Grimme

Did you check if the speed and the bits-per-word were set correctly? Could you provide an example code, so we could reproduce this issue?

Thanks and best regards, Jaski

Hi Jaski,

you want more code? Here it is.

spitest-rt.c:

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include "spi.h"
#include "gpio.h"

#define SPI_SPEED 8312500
#define SPI_BITS_PER_WORD 32
#define BUFF_SIZE 256
#define GPIO_PIN_NO 7
#define SLEEP_NSECS 200000
#define NSECS_PER_SEC 1000000000
//#define PERIOD_NSECS 1000000 // 1ms
#define PERIOD_NSECS 500000 // 500us

#define THREAD_PRIORITY 50


int add_nsecs(struct timespec *ts, long nsec)
{
    ts->tv_nsec+=nsec;
    while(ts->tv_nsec > NSECS_PER_SEC)
    {
        ts->tv_nsec-=NSECS_PER_SEC;
        ts->tv_sec++;
    }
    return 0;
}

void *spi_thread(void *par)
{
    int fd;
    char buff_tx[BUFF_SIZE];
    char buff_rx[BUFF_SIZE];
    GPIO_PIN_DAT gpio_pin;
    struct timespec req;

    fd=spi_open("/dev/spidev3.0", 0, SPI_BITS_PER_WORD, SPI_SPEED);
    if(fd < 0)
    {
        fprintf(stderr,"failed to open device\n");
        return (void *)-1;
    }
    gpio_pin.gpio_pin_no=GPIO_PIN_NO;
    gpio_pin.gpio_set_dir=GPIO_DIR_OUT;
    gpio_setup(&gpio_pin);

    memset(buff_tx, 0xaa, BUFF_SIZE);
    gpio_write_pin(&gpio_pin, LOW);
    clock_gettime(CLOCK_MONOTONIC, &req);

    while(1)
    {
        gpio_write_pin(&gpio_pin, HIGH);
        if(spi_transfer(fd, buff_tx, buff_rx, BUFF_SIZE) < 0)
            fprintf(stderr,"failed to transfer\n");
        gpio_write_pin(&gpio_pin, LOW);
        add_nsecs(&req, PERIOD_NSECS);
        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, NULL);
    }

    gpio_close(&gpio_pin);
    spi_close(fd);
    return NULL;
}

int main(int argc, char **argv)
{
    pthread_attr_t attr;
    struct sched_param sched;
    pthread_t thread;

    // set somethread attributes
    pthread_attr_init(&attr);
    pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
    sched.sched_priority=THREAD_PRIORITY;
    pthread_attr_setschedparam(&attr, &sched);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    // start the thread
    pthread_create(&thread, &attr, spi_thread, NULL);
    // wait for thread to terminate
    pthread_join(thread, NULL);

    return 0;
}

spi.h:

#ifndef SPI_H
#include <stdint.h>
#include <unistd.h>

int spi_open(char *, uint32_t, uint8_t, uint32_t);
ssize_t spi_transfer(int , void *, void *, size_t);
int spi_close(int);

#endif

spi.c:

#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <string.h>

#include "spi.h"

int spi_open(char *name, uint32_t mode, uint8_t bits, uint32_t speed)
{
    int fd;
    int err;

    fd=open(name, O_RDWR | O_NONBLOCK);
    if(fd < 0)
        return -1;

    err=ioctl(fd, SPI_IOC_RD_MODE32, &mode);
    if(!err)
        err=ioctl(fd, SPI_IOC_WR_MODE32, &mode);
    if(!err)
        err=ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
    if(!err)
        err=ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    if(!err)
        err=ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
    if(!err)
        err=ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

    if(!err)
        return fd;
    close(fd);
    return -1;
}

ssize_t spi_transfer(int fd, void *out, void *in, size_t len)
{
    struct spi_ioc_transfer buff;

    memset(&buff, 0, sizeof(buff));
    buff.tx_buf=(__u64)out;
    buff.rx_buf=(__u64)in;
    buff.len=len;
    buff.delay_usecs=0;
    if(ioctl(fd, SPI_IOC_MESSAGE(1), &buff) < 0)
        return -1;
    return len;
}

int spi_close(int fd)
{
    return close(fd);
}

gpio.h:

#ifndef GPIO_H
#define GPIO_H

#include <stdint.h>

// maybe enum
#define GPIO_DIR_OUT 1
#define GPIO_DIR_IN  0

// enum
#ifndef HIGH
#define HIGH (1)
#endif

#ifndef LOW
#define LOW (0)
#endif

#ifndef BOOL
typedef int BOOL;
#endif

#ifndef TRUE
#define FALSE 0
#define TRUE (!FALSE)
#endif

typedef struct
{
    uint32_t    gpio_value;
    uint32_t    gpio_pin_no;    // number of the pin
    int         gpio_pin_fd;    // filedescriptor of the value file of the pin
    uint32_t    gpio_pin_mask;  // only for backward compatibility
                                // because it is frequently used in the code
    uint32_t    gpio_set_dir;
} GPIO_PIN_DAT;

uint32_t gpio_setup( GPIO_PIN_DAT *); // better use BOOL
void gpio_write( GPIO_PIN_DAT *);
int32_t gpio_read( GPIO_PIN_DAT *); // negative return indicates an error
void gpio_write_pin( GPIO_PIN_DAT *, uint32_t);
// additional functions
void gpio_close( GPIO_PIN_DAT *);

#endif

gpio.c:

#include <stdio.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>

#include "gpio.h"

static const char *gpio_file_templ="/sys/class/gpio/gpio%d/%s";

//*----------------------------------------------------------------------------
//* Function Name       : gpio_setup
//* Object              : define a direction for a pin and open the value file
//* Input Parameters    : p_dat: pointer to pin data
//*                       
//*                     : 
//* Output Parameters   : FALSE on error
//+ Date                : 25.07.2018  Dietmar Muscholik
//*----------------------------------------------------------------------------
uint32_t gpio_setup( GPIO_PIN_DAT *p_dat)
{
    uint32_t ret = FALSE;
    char path[PATH_MAX];
    int fd;

    snprintf(path, sizeof(path), gpio_file_templ, p_dat->gpio_pin_no, "direction");
    fd=open(path, O_WRONLY);
    if(fd > 0)
    {
        switch(p_dat->gpio_set_dir)
        {
        case GPIO_DIR_IN:
            if(write(fd, "in", 2) > 0)
                ret = TRUE;
            break;
        case GPIO_DIR_OUT:
            if(write(fd, "out", 3) > 0)
                ret = TRUE;
            break;
        }
        close(fd);
        if(ret)
        {
            snprintf(path, sizeof(path), gpio_file_templ, p_dat->gpio_pin_no, "value");
            p_dat->gpio_pin_fd=open(path, O_RDWR);
            if(p_dat->gpio_pin_fd < 0)
                ret=FALSE;
        }
    }

    return ret;
}


//*----------------------------------------------------------------------------
//* Function Name       : gpio_write
//* Object              : backward compatibility, better use gpio_write_pin
//* Input Parameters    : p_dat: pointer to pin data
//*                       
//*                     : 
//* Output Parameters   : none
//+ Date                : 25.07.2018  Dietmar Muscholik
//*----------------------------------------------------------------------------
void gpio_write( GPIO_PIN_DAT *p_dat)
{
    if(p_dat->gpio_value)
        write(p_dat->gpio_pin_fd, "1", 1);
    else
        write(p_dat->gpio_pin_fd, "0", 1);
}

//*----------------------------------------------------------------------------
//* Function Name       : gpio_read
//* Object              : read the state of a pin
//* Input Parameters    : p_dat: pointer to pin data
//*                       
//*                     : 
//* Output Parameters   : state of the pin (HIGH or LOW)
//+ Date                : 25.07.2018  Dietmar Muscholik
//*----------------------------------------------------------------------------
int32_t gpio_read( GPIO_PIN_DAT *p_dat)
{
    char buff[2];

    lseek(p_dat->gpio_pin_fd,0, SEEK_SET);
    if(read(p_dat->gpio_pin_fd, buff, sizeof(buff)) > 0)
        return *buff=='1' ? HIGH : LOW;
    return -1;
}


//*----------------------------------------------------------------------------
//* Function Name       : gpio_write
//* Object              : write to a pin
//* Input Parameters    : p_dat: pointer to pin data
//*                       state: either HIGH or LOW
//*                     : 
//* Output Parameters   : none
//+ Date                : 25.07.2018  Dietmar Muscholik
//*----------------------------------------------------------------------------
void gpio_write_pin( GPIO_PIN_DAT *p_dat, uint32_t state)
{
    if(state)   // everything but LOW is HIGH
        write(p_dat->gpio_pin_fd, "1", 1);
    else
        write(p_dat->gpio_pin_fd, "0", 1);
}
//*----------------------------------------------------------------------------
//* Function Name       : gpio_close
//* Object              : close the value file
//* Input Parameters    : none
//*                       
//*                     : 
//* Output Parameters   : none
//+ Date                : 25.07.2018  Dietmar Muscholik
//*----------------------------------------------------------------------------
void gpio_close( GPIO_PIN_DAT *p_dat)
{
    close(p_dat->gpio_pin_fd);
}

Makefile:

CFLAGS=-I$(SYSROOT)/usr/include -O2 -Wall
LDFLAGS=-L$(SYSROOT)/usr/lib -lpthread -lrt

spitest-rt: spitest-rt.o spi.o gpio.o
clean:
    -rm spitest-rt spitest-rt.o spi.o gpio.o

That’s all I have.

Best regards,

Grimme

@Grimme

Thanks for the files. Which Cross-Compiler ToolsChain (version) did you use to compile those files?

Hi Jaski,

I used gcc-linaro-6.2.1-2016.11-i686_arm-linux-gnueabihf.tar.xz for cross-compiling like recommended here.

Thanks Grimme

Hi @Grimme

Thanks for the Information. Actually in the spi.c, you are first reading the value to mode, … and then writing it back. It should be first writing and then reading back.

 err=ioctl(fd, SPI_IOC_RD_MODE32, &mode);
 if(!err)
     err=ioctl(fd, SPI_IOC_WR_MODE32, &mode);

Best regards
Jaski

Hi Jaski,

mea culpa, I blindly copied some example code.
Now I corrected it and it works.

Thanks
Grimme

Perfect that it works. Thanks for the feedback.