User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Faster SPI

Sun Aug 10, 2014 12:17 pm

Attached is some code which drives the SPI peripheral via its registers. It should allow reading of SPI devices at close to their design limits. For instance it allows for reading 100ksps from the MCP3202 which has a rating of 100ksps at 5V.

It also allows 3-wire operation in a limited fashion, i.e. single wire for MOSI and MISO. In 3-wire mode the message is split up into two parts, in the first part the single wire is driven as MOSI, in the second part the bus is reversed and the single wire is driven as MISO. The software provided only allows the turnaround on a byte boundary. The code contains an example of driving the MCP4131 which needs to be driven in this way (at least if you want to read from it).

There are 3 SPI functions.
  • void spiInit(void) - saves current SPI state and sets up SPI mode.
  • void spiTerm(void) - restores the original SPI state.
  • void spiXfer(
    unsigned speed, /* bits per second */
    unsigned channel, /* 0 or 1 */
    unsigned mode, /* 0 - 3 */
    unsigned cspol, /* 0 = active low, 1 = active high */
    char *txBuf, /* tx buffer */
    char *rxBuf, /* rx buffer */
    unsigned cnt4w, /* number of 4-wire bytes */
    unsigned cnt3w) /* number of 3-wire bytes */

Code: Select all

/*
2014-08-10

gcc -o minimal_spi minimal_spi.c -lrt
sudo ./minimal_spi
*/

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

/* Peripherals */

#define SYST_BASE  0x20003000
#define DMA_BASE   0x20007000
#define PADS_BASE  0x20100000
#define CLK_BASE   0x20101000
#define GPIO_BASE  0x20200000
#define UART0_BASE 0x20201000
#define PCM_BASE   0x20203000
#define SPI_BASE   0x20204000
#define I2C0_BASE  0x20205000
#define PWM_BASE   0x2020C000
#define UART1_BASE 0x20215000
#define I2C1_BASE  0x20804000
#define I2C2_BASE  0x20805000
#define DMA15_BASE 0x20E05000

#define DMA_LEN   0x1000 /* allow access to all channels */
#define CLK_LEN   0xA8
#define GPIO_LEN  0xB4
#define SYST_LEN  0x1C
#define PCM_LEN   0x24
#define PWM_LEN   0x28
#define I2C_LEN   0x1C
#define SPI_LEN   0x18
#define PADS_LEN  0x1000

/* GPIOs */

#define GPSET0 7
#define GPSET1 8

#define GPCLR0 10
#define GPCLR1 11

#define GPLEV0 13
#define GPLEV1 14

#define GPPUD     37
#define GPPUDCLK0 38
#define GPPUDCLK1 39

/* System 1us clock */

#define SYST_CS  0
#define SYST_CLO 1
#define SYST_CHI 2

/* SPI */

#define SPI_CS   0
#define SPI_FIFO 1
#define SPI_CLK  2
#define SPI_DLEN 3
#define SPI_LTOH 4
#define SPI_DC   5

#define SPI_CS_LEN_LONG  (1<<25)
#define SPI_CS_DMA_LEN   (1<<24)
#define SPI_CS_CSPOL2    (1<<23)
#define SPI_CS_CSPOL1    (1<<22)
#define SPI_CS_CSPOL0    (1<<21)
#define SPI_CS_RXF       (1<<20)
#define SPI_CS_RXR       (1<<19)
#define SPI_CS_TXD       (1<<18)
#define SPI_CS_RXD       (1<<17)
#define SPI_CS_DONE      (1<<16)
#define SPI_CS_LEN       (1<<13)
#define SPI_CS_REN       (1<<12)
#define SPI_CS_ADCS      (1<<11)
#define SPI_CS_INTR      (1<<10)
#define SPI_CS_INTD      (1<<9)
#define SPI_CS_DMAEN     (1<<8)
#define SPI_CS_TA        (1<<7)
#define SPI_CS_CSPOL(x)  ((x)<<6)
#define SPI_CS_CLEAR(x)  ((x)<<4)
#define SPI_CS_MODE(x)   ((x)<<2)
#define SPI_CS_CS(x)     ((x)<<0)

#define SPI_DC_RPANIC(x) ((x)<<24)
#define SPI_DC_RDREQ(x)  ((x)<<16)
#define SPI_DC_TPANIC(x) ((x)<<8)
#define SPI_DC_TDREQ(x)  ((x)<<0)

#define SPI_MODE0 0
#define SPI_MODE1 1
#define SPI_MODE2 2
#define SPI_MODE3 3

#define SPI_CS0     0
#define SPI_CS1     1
#define SPI_CS2     2

static volatile uint32_t *gpioReg = MAP_FAILED;
static volatile uint32_t *systReg = MAP_FAILED;
static volatile uint32_t *pwmReg  = MAP_FAILED;
static volatile uint32_t *clkReg  = MAP_FAILED;
static volatile uint32_t *padsReg = MAP_FAILED;
static volatile uint32_t *spiReg  = MAP_FAILED;
static volatile uint32_t *i2c0Reg = MAP_FAILED;
static volatile uint32_t *i2c1Reg = MAP_FAILED;

#define PI_BANK (gpio>>5)
#define PI_BIT  (1<<(gpio&0x1F))

/* SPI gpios. */

#define PI_SPI_CE0   8
#define PI_SPI_CE1   7
#define PI_SPI_SCLK 11
#define PI_SPI_MISO  9
#define PI_SPI_MOSI 10

/* gpio levels. */

#define PI_LOW  0
#define PI_HIGH 1

/* gpio modes. */

#define PI_INPUT  0
#define PI_OUTPUT 1
#define PI_ALT0   4
#define PI_ALT1   5
#define PI_ALT2   6
#define PI_ALT3   7
#define PI_ALT4   3
#define PI_ALT5   2

void gpioSetMode(unsigned gpio, unsigned mode)
{
   int reg, shift;

   reg   =  gpio/10;
   shift = (gpio%10) * 3;

   gpioReg[reg] = (gpioReg[reg] & ~(7<<shift)) | (mode<<shift);
}

int gpioGetMode(unsigned gpio)
{
   int reg, shift;

   reg   =  gpio/10;
   shift = (gpio%10) * 3;

   return (*(gpioReg + reg) >> shift) & 7;
}

/* Values for pull-ups/downs off, pull-down and pull-up. */

#define PI_PUD_OFF  0
#define PI_PUD_DOWN 1
#define PI_PUD_UP   2

void gpioSetPullUpDown(unsigned gpio, unsigned pud)
{
   *(gpioReg + GPPUD) = pud;

   usleep(20);

   *(gpioReg + GPPUDCLK0 + PI_BANK) = PI_BIT;

   usleep(20);
  
   *(gpioReg + GPPUD) = 0;

   *(gpioReg + GPPUDCLK0 + PI_BANK) = 0;
}

int gpioRead(unsigned gpio)
{
   if ((*(gpioReg + GPLEV0 + PI_BANK) & PI_BIT) != 0) return 1;
   else                                         return 0;
}

void gpioWrite(unsigned gpio, unsigned level)
{
   if (level == 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
   else            *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;
}

void gpioTrigger(unsigned gpio, unsigned pulseLen, unsigned level)
{
   if (level == 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
   else            *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;

   usleep(pulseLen);

   if (level != 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
   else            *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;
}

/* Bit (1<<x) will be set if gpio x is high. */

uint32_t gpioReadBank1(void) { return (*(gpioReg + GPLEV0)); }
uint32_t gpioReadBank2(void) { return (*(gpioReg + GPLEV1)); }

/* To clear gpio x bit or in (1<<x). */

void gpioClearBank1(uint32_t bits) { *(gpioReg + GPCLR0) = bits; }
void gpioClearBank2(uint32_t bits) { *(gpioReg + GPCLR1) = bits; }

/* To set gpio x bit or in (1<<x). */

void gpioSetBank1(uint32_t bits) { *(gpioReg + GPSET0) = bits; }
void gpioSetBank2(uint32_t bits) { *(gpioReg + GPSET1) = bits; }

unsigned gpioHardwareRevision(void)
{
   static unsigned rev = 0;

   FILE * filp;
   char buf[512];
   char term;

   if (rev) return rev;

   filp = fopen ("/proc/cpuinfo", "r");

   if (filp != NULL)
   {
      while (fgets(buf, sizeof(buf), filp) != NULL)
      {
         if (!strncasecmp("revision\t", buf, 9))
         {
            if (sscanf(buf+strlen(buf)-5, "%x%c", &rev, &term) == 2)
            {
               if (term == '\n') break;
               rev = 0;
            }
         }
      }
      fclose(filp);
   }
   return rev;
}

/* Returns the number of microseconds after system boot. Wraps around
   after 1 hour 11 minutes 35 seconds.
*/

uint32_t gpioTick(void) { return systReg[SYST_CLO]; }


double time_time(void)
{
   struct timeval tv;
   double t;

   gettimeofday(&tv, 0);

   t = (double)tv.tv_sec + ((double)tv.tv_usec / 1E6);

   return t;
}

void time_sleep(double seconds)
{
   struct timespec ts, rem;

   if (seconds > 0.0)
   {
      ts.tv_sec = seconds;
      ts.tv_nsec = (seconds-(double)ts.tv_sec) * 1E9;

      while (clock_nanosleep(CLOCK_REALTIME, 0, &ts, &rem))
      {
         /* copy remaining time to ts */
         ts.tv_sec  = rem.tv_sec;
         ts.tv_nsec = rem.tv_nsec;
      }
   }
}

/*SPI */

static unsigned old_mode_ce0;
static unsigned old_mode_ce1;
static unsigned old_mode_sclk;
static unsigned old_mode_miso;
static unsigned old_mode_mosi;

static uint32_t old_spi_cs;
static uint32_t old_spi_clk;

void spiInit(void)
{
   old_mode_ce0  = gpioGetMode(PI_SPI_CE0);
   old_mode_ce1  = gpioGetMode(PI_SPI_CE1);
   old_mode_sclk = gpioGetMode(PI_SPI_SCLK);
   old_mode_miso = gpioGetMode(PI_SPI_MISO);
   old_mode_mosi = gpioGetMode(PI_SPI_MOSI);

   gpioSetMode(PI_SPI_CE0, PI_ALT0);
   gpioSetMode(PI_SPI_CE1, PI_ALT0);
   gpioSetMode(PI_SPI_SCLK, PI_ALT0);
   gpioSetMode(PI_SPI_MISO, PI_ALT0);
   gpioSetMode(PI_SPI_MOSI, PI_ALT0);

   old_spi_cs  = spiReg[SPI_CS];
   old_spi_clk = spiReg[SPI_CLK];
}

void spiTerm(void)
{  
   gpioSetMode(PI_SPI_CE0, old_mode_ce0);
   gpioSetMode(PI_SPI_CE1, old_mode_ce1);
   gpioSetMode(PI_SPI_SCLK, old_mode_sclk);
   gpioSetMode(PI_SPI_MISO, old_mode_miso);
   gpioSetMode(PI_SPI_MOSI, old_mode_mosi);

   spiReg[SPI_CS]  = old_spi_cs;
   spiReg[SPI_CLK] = old_spi_clk;
}

void spiXfer(
   unsigned speed,    /* bits per second        */
   unsigned channel,  /* 0 or 1                 */
   unsigned mode,     /* 0 - 3                  */
   unsigned cspol,    /* 0 = active low         */
   char     *txBuf,   /* tx buffer              */
   char     *rxBuf,   /* rx buffer              */
   unsigned cnt4w,    /* number of 4-wire bytes */
   unsigned cnt3w)    /* number of 3-wire bytes */
{
   unsigned txCnt=0;
   unsigned rxCnt=0;
   unsigned cnt;
   uint32_t spiDefaults;

   spiDefaults = SPI_CS_MODE(mode)   |
                 SPI_CS_CS(channel)  |
                 SPI_CS_CSPOL(cspol) |
                 SPI_CS_CLEAR(3);

   spiReg[SPI_CLK] = 250000000/speed;

   spiReg[SPI_CS] = spiDefaults | SPI_CS_TA; /* start */

   cnt = cnt4w;

   while((txCnt < cnt) || (rxCnt < cnt))
   {
      while((txCnt < cnt) && ((spiReg[SPI_CS] & SPI_CS_TXD)))
      {
         spiReg[SPI_FIFO] = txBuf[txCnt++];
      }

      while((rxCnt < cnt) && ((spiReg[SPI_CS] & SPI_CS_RXD)))
      {
         rxBuf[rxCnt++] = spiReg[SPI_FIFO];
      }
   }

   while (!(spiReg[SPI_CS] & SPI_CS_DONE)) ;

   /* now switch to 3-wire bus */

   cnt += cnt3w;

   while((txCnt < cnt) || (rxCnt < cnt))
   {
      spiReg[SPI_CS] |= SPI_CS_REN;

      while((txCnt < cnt) && ((spiReg[SPI_CS] & SPI_CS_TXD)))
      {
         spiReg[SPI_FIFO] = txBuf[txCnt++];
      }

      while((rxCnt < cnt) && ((spiReg[SPI_CS] & SPI_CS_RXD)))
      {
         rxBuf[rxCnt++] = spiReg[SPI_FIFO];
      }
   }

   while (!(spiReg[SPI_CS] & SPI_CS_DONE)) ;

   spiReg[SPI_CS] = spiDefaults; /* stop */
}

/* Map in registers. */

static uint32_t * initMapMem(int fd, uint32_t addr, uint32_t len)
{
    return (uint32_t *) mmap(0, len,
       PROT_READ|PROT_WRITE|PROT_EXEC,
       MAP_SHARED|MAP_LOCKED,
       fd, addr);
}

int gpioInitialise(void)
{
   int fd;

   fd = open("/dev/mem", O_RDWR | O_SYNC) ;

   if (fd < 0)
   {
      fprintf(stderr,
         "This program needs root privileges.  Try using sudo\n");
      return -1;
   }

   gpioReg = initMapMem(fd, GPIO_BASE, GPIO_LEN);
   systReg = initMapMem(fd, SYST_BASE, SYST_LEN);
   pwmReg  = initMapMem(fd, PWM_BASE,  PWM_LEN);
   clkReg  = initMapMem(fd, CLK_BASE,  CLK_LEN);
   padsReg = initMapMem(fd, PADS_BASE, PADS_LEN);
   spiReg  = initMapMem(fd, SPI_BASE,  SPI_LEN);

   close(fd);

   if ((gpioReg == MAP_FAILED) || (systReg == MAP_FAILED))
   {
      fprintf(stderr,
         "Bad, mmap failed\n");
      return -1;
   }
   return 0;
}

int read_mcp3202(
   unsigned speed,
   unsigned spi_channel,
   unsigned mode,
   unsigned adc_channel)
{
   const int msglen = 3;

   char txBuf[msglen];
   char rxBuf[msglen];

   txBuf[0] = 1;
   if (adc_channel == 0) txBuf[1] = 0x80; else txBuf[1] = 0xC0;
   txBuf[2] = 0;

   /* speed, channel, mode, cspol, *txBuf,*rxBuf,cnt4w,cnt3w */

   spiXfer(speed, spi_channel, mode, 0, txBuf, rxBuf, msglen, 0); /* SPI xfer */

   return ((rxBuf[1]&0x0F)<<8) + rxBuf[2];
}

int mcp3202_test(int argc, char *argv[])
{
   int i;

   int speed, iters, maxsps, pf;

   unsigned divider;

   unsigned reading[4096];
   unsigned val;

   double start, duration;

   uint32_t nt, micros;
   int tdiff;

   if (argc >= 4)
   {
      speed  = atoi(argv[1]);
      iters  = atoi(argv[2]);
      maxsps = atoi(argv[3]);
      micros = 1000000 / maxsps;
      if (argc > 4) pf = 1; else pf = 0;
   }
   else
   {
      fprintf(stderr, "need Speed(bps), Samples, Max sps.\n");
      fprintf(stderr, "sudo ./spi 2500000 1000000 100000\n");
      return 1;
   }

   for (i=0; i<4096; i++) reading[i]= 0;

   /* An MCP3202 is being used for testing.  Read channel 0 */
   /* Max speed 2.5MHz clock, 100ksps @ 5V                  */

   divider = 250000000/speed;
   if (divider % 1) divider++;

   spiInit(); /* save old SPI settings, initialise */

   printf ("selected speed=%d (set=%u) iters=%d xfer=3 mingap=%d\n",
      speed, 250000000/divider, iters, micros);

   start = time_time();

   for (i=0; i<iters; i++)
   {
      nt = gpioTick() + micros;

      val = read_mcp3202(speed, 1, 0, 0);

      if (pf) printf("%u\n", val);

      reading[(val/10)*10]++;

      do /* rate limit */
      {
         tdiff = nt - gpioTick();
      } while (tdiff > 0);
   }

   duration = time_time()  - start;

   spiTerm(); /* restore old SPI settings */

   for (i=0; i<4096; i++)
   {
      if (reading[i]) printf("%d %d\n", i, reading[i]);
   }

   printf("%.0f sps over %.1f seconds\n", ((float)iters)/(duration), duration);

   return 0;
}

int mcp4131_test(int argc, char *argv[])
{
   int i;

   int speed, iters, maxsps, pf;

   const int msglen = 2;
   const int maxset = 129;
   const int maxreading = 1024;
   unsigned divider;
   char txBuf[msglen];
   char rxBuf[msglen];

   unsigned reading[maxreading];

   unsigned val;

   double start, duration;

   uint32_t nt, micros;
   int tdiff;

   if (argc >= 4)
   {
      speed  = atoi(argv[1]);
      iters  = atoi(argv[2]);
      maxsps = atoi(argv[3]);
      micros = 1000000 / maxsps;
      if (argc > 4) pf = 1; else pf = 0;
   }
   else
   {
      fprintf(stderr, "need Speed(bps), Samples, Max sps.\n");
      fprintf(stderr, "sudo ./spi 2500000 1000000 100000\n");
      return 1;
   }

   divider = 250000000/speed;
   if (divider % 1) divider++;

   for (i=0; i<maxreading; i++) reading[i] = 0;

   spiInit(); /* save old SPI settings, initialise */

   printf ("selected speed=%d (set=%u) iters=%d xfer=%d mingap=%d\n",
      speed, 250000000/divider, iters, msglen, micros);

   start = time_time();

   for (i=0; i<iters; i++)
   {
      nt = gpioTick() + micros;

      /* set wiper 0 position */

      txBuf[0] = 0; /* write wiper 0 */
      txBuf[1] = i%maxset;

      /* speed, channel, mode, cspol, *txBuf,*rxBuf,cnt4w,cnt3w */

      spiXfer(speed, 0, 0, 0, txBuf, rxBuf, 2, 0); /* SPI xfer */

      /* read position back */

      usleep(1);

      /* see what mcp3202 thinks */

      val = read_mcp3202(1000000, 1, 0, 1);

      usleep(1);

      txBuf[0] = 0x0C; /* read wiper 0 */
      txBuf[1] = 0;

      rxBuf[1] = 0;

      spiXfer(250000, 0, 0, 0, txBuf, rxBuf, 1, 1); /* SPI xfer */

      if (pf) printf("%u %u (%u)\n", i%maxset, rxBuf[1], val);

      do /* rate limit */
      {
         tdiff = nt - gpioTick();
      } while (tdiff > 0);
   }

   duration = time_time()  - start;

   spiTerm(); /* restore old SPI settings */

   for (i=0; i<maxreading; i++)
   {
      if (reading[i]) printf("%d %d\n", i, reading[i]);
   }

   printf("%.0f sps over %.1f seconds\n", ((float)iters)/(duration), duration);

   return 0;
}

main(int argc, char *argv[])
{
   if (gpioInitialise() < 0) return 1;

   return mcp3202_test(argc, argv);
}
The following session shows a MCP3202 being pushed to its limits.

Code: Select all

[email protected] /ram $ gcc -o spi /code/minimal_spi.c
[email protected] /ram $ sudo ./spi 2500000 1000000 100000
selected speed=2500000 (set=2500000) iters=1000000 xfer=3 mingap=10
1960 2
1970 780
1980 249006
1990 748608
2000 1601
2010 3
82947 sps over 12.1 seconds
[email protected] /ram $ sudo ./spi 2500000 1000000 120000
selected speed=2500000 (set=2500000) iters=1000000 xfer=3 mingap=8
1960 1
1970 722
1980 248707
1990 748981
2000 1584
2010 5
83114 sps over 12.0 seconds
[email protected] /ram $ sudo ./spi 2800000 1000000 100000
selected speed=2800000 (set=2808988) iters=1000000 xfer=3 mingap=10
1950 1
1960 24696
1970 244841
1980 719757
1990 10703
2000 2
93338 sps over 10.7 seconds
[email protected] /ram $ sudo ./spi 2900000 1000000 100000
selected speed=2900000 (set=2906976) iters=1000000 xfer=3 mingap=10
1950 2
1960 74271
1970 379636
1980 546042
1990 49
95097 sps over 10.5 seconds
[email protected] /ram $ sudo ./spi 3000000 1000000 100000
selected speed=3000000 (set=3012048) iters=1000000 xfer=3 mingap=10
1940 4
1950 78
1960 254656
1970 713080
1980 32172
1990 10
98910 sps over 10.1 seconds
[email protected] /ram $ sudo ./spi 3100000 1000000 100000
selected speed=3100000 (set=3125000) iters=1000000 xfer=3 mingap=10
1940 4
1950 14047
1960 476207
1970 509603
1980 131
1990 8
98965 sps over 10.1 seconds
[email protected] /ram $ sudo ./spi 3100000 1000000 110000
selected speed=3100000 (set=3125000) iters=1000000 xfer=3 mingap=9
1950 13470
1960 476608
1970 509795
1980 126
1990 1
101066 sps over 9.9 seconds
[email protected] /ram $ 
Of course you can push a chip beyond its limits. Just don't expect usable results.

Code: Select all

[email protected] /ram $ sudo ./spi 125000000 1000000 1000000
selected speed=125000000 (set=125000000) iters=1000000 xfer=3 mingap=1
0 1000000
494387 sps over 2.0 seconds
[email protected] /ram $ 

User avatar
mikronauts
Posts: 2705
Joined: Sat Jan 05, 2013 7:28 pm
Contact: Website

Re: Faster SPI

Sun Aug 10, 2014 4:11 pm

Great work Joan!

This will help a lot of folks.
http://Mikronauts.com - home of EZasPi, RoboPi, Pi Rtc Dio and Pi Jumper @Mikronauts on Twitter
Advanced Robotics, I/O expansion and prototyping boards for the Raspberry Pi

Andrew_ww
Posts: 8
Joined: Wed May 23, 2012 11:30 am

Re: Faster SPI

Wed Aug 13, 2014 7:40 am

Sry for newbie question, what is supposed to be design limit for rasp spi interface 2.5 mega bit per second or 25 mega bit per second? A bit confused about this "ksps". Thank you.

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Wed Aug 13, 2014 8:25 am

Andrew_ww wrote:Sry for newbie question, what is supposed to be design limit for rasp spi interface 2.5 mega bit per second or 25 mega bit per second? A bit confused about this "ksps". Thank you.
The Raspberry Pi SPI interface can be driven at 125 Mbps (million bits per second). This equates to a clock of 4 nanoseconds on, 4 nanoseconds off. A bit is transferred for each clock, 8 nanoseconds, so 125 Mbps.

Of course it's not much use driving the SPI interface that fast if the chip you are trying to talk to can't handle those speeds.

The MCP3202 chip I was using for tests specifies a minimum clock of 250 nanoseconds on and 250 nanoseconds off which equates to a 2 Mbps maximum speed. The MCP3202, an ADC, also specifies that it can digitise samples at 100 ksps (thousand samples per second) if supplied with 5V.

Given that you normally need to transfer 3 bytes (24 bits) per MCP3202 sample you can see that the in specification maximum sample rate is 2 million / 24 or 83.3 ksps. We have to drive it beyond its specifications to reach 100 ksps.

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Wed Aug 13, 2014 8:33 am

The latest version (18) of pigpio uses this method, direct register access, for SPI.

These speeds will only be achievable using the C library, NOT from Python.

The ability to use 3-wire (albeit in a limited way) may be more useful than sampling at rates faster than the Pi can process the data.

Andrew_ww
Posts: 8
Joined: Wed May 23, 2012 11:30 am

Re: Faster SPI

Wed Aug 13, 2014 9:20 am

Thank you for precise information. I really appreciate it.

wietjes
Posts: 14
Joined: Tue Feb 16, 2016 7:13 pm

Re: Faster SPI

Wed Feb 17, 2016 9:20 am

Hi joan, how can I use the PIGPIO library to obtain these speeds?
I'm trying this with an MPC3008 where do I have to change the code you posted?
Would this also work with the LTC2315-12? It's a faster ADC but doesn't has a MISO..

Thanks in advance!

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Sun Feb 21, 2016 9:48 am

wietjes wrote:Hi joan, how can I use the PIGPIO library to obtain these speeds?
I'm trying this with an MPC3008 where do I have to change the code you posted?
Would this also work with the LTC2315-12? It's a faster ADC but doesn't has a MISO..

Thanks in advance!
I incorporated that code into pigpio.

For those sorts of speeds you would need to link directly against the C library and use the spi* functions, e.g. spiOpen, spiXfer etc.

User avatar
karrika
Posts: 1051
Joined: Mon Oct 19, 2015 6:21 am
Location: Finland

Re: Faster SPI

Sun Feb 21, 2016 9:58 am

What a shame that the max clock of enc28j60 is only 20MHz.

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Sun Feb 21, 2016 10:11 am

karrika wrote:What a shame that the max clock of enc28j60 is only 20MHz.
I don't know how fast you can push the GPIO. 31.25 MHz probably works. The next speeds are 41.7, 62.5 , and 125 MHz. I don't know if any of them can be made to work.

For most people who want high SPS the bottleneck is calls per second not transfer rate in bps.

wietjes
Posts: 14
Joined: Tue Feb 16, 2016 7:13 pm

Re: Faster SPI

Thu Feb 25, 2016 12:24 pm

joan wrote:
wietjes wrote:Hi joan, how can I use the PIGPIO library to obtain these speeds?
I'm trying this with an MPC3008 where do I have to change the code you posted?
Would this also work with the LTC2315-12? It's a faster ADC but doesn't has a MISO..

Thanks in advance!
I incorporated that code into pigpio.

For those sorts of speeds you would need to link directly against the C library and use the spi* functions, e.g. spiOpen, spiXfer etc.
Thanks for the quick answer!
Do you know if it's possible to use Pigpio in Simulink like you can with wiringpi?

scotty101
Posts: 3597
Joined: Fri Jun 08, 2012 6:03 pm

Re: Faster SPI

Thu Feb 25, 2016 1:18 pm

wietjes wrote:Do you know if it's possible to use Pigpio in Simulink like you can with wiringpi?
Mathworks have specifically written the Simulink blocks to work with wiringPi. However the source code for that is open so you could create your own Simulink blocks that use pigpio and re-distribute that to the community.
Shouldn't be too difficult.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

wietjes
Posts: 14
Joined: Tue Feb 16, 2016 7:13 pm

Re: Faster SPI

Fri Feb 26, 2016 2:53 pm

joan wrote:
wietjes wrote:Hi joan, how can I use the PIGPIO library to obtain these speeds?
I'm trying this with an MPC3008 where do I have to change the code you posted?
Would this also work with the LTC2315-12? It's a faster ADC but doesn't has a MISO..

Thanks in advance!
I incorporated that code into pigpio.

For those sorts of speeds you would need to link directly against the C library and use the spi* functions, e.g. spiOpen, spiXfer etc.
Is there C-code available for reading the values of the MCP3008 with the pigpio-library?
Sorry I'm a real novice in these things :)

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Fri Feb 26, 2016 3:13 pm

I don't provide any wrappers for particular chips as part of the library. In effect it would be a wrapper around two or three lines of code which will vary from device to device.

wiringPi does provide wrappers for a lot of chips.

cal-linux
Posts: 3
Joined: Thu Feb 25, 2016 4:44 pm

Re: Faster SPI

Sat Feb 27, 2016 12:54 am

Hi Joan,

Two questions:

1)
I wonder whether your functions could be used in bare metal mode?
The only change I can see is the spiReg; instead of opening /dev/mem
and attaching, I would simply initialize it:

static volatile uint32_t * spiReg = SPI_BASE;

Correct?

2)
I'm a bit puzzled about this: in spiXfer, after starting the transfer
(the line spiReg[SPI_CS] = spiDefaults | SPI_CS_TA ), I would
expect to see something like this:

while ( tx_buffer_full() ) {} // busy-loop waiting until tx has room
write_to_tx ( ... );

The way it's written, it suggests that you don't want to block for TX
when maybe RX has data, or vice-versa.... however, shouldn't that
be with if's instead of while's?

Code: Select all

while ((txCnt < cnt) || (rxCnt < cnt))
{
    if (! tx_buffer_full())
    {
        transmit something and increase txCnt;
    }
    if (rx_buffer_has_data())
    {
        read and increase rxCnt;
    }
}
Am I misunderstanding the code?

Thanks,
Cal-linux
--

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Sat Feb 27, 2016 9:13 am

@cal-linux

Yes, it should work in bare metal with a few changes.

On fill/empty buffer. I reckon you would end up putting a while loop within your if statements to fill/empty the buffer as quickly as possible. The code as is just seems to do away with an if to get straight into the while.

User avatar
Arjan
Posts: 261
Joined: Sat Sep 08, 2012 1:59 pm

Re: Faster SPI

Sun Feb 28, 2016 5:58 pm

Hi cal-linux,

I have ported the C library for Raspberry Pi (RPi) to bare-metal.
This makes it easy to test your application in Linux, and run the same code on bare-metal (https://github.com/vanvught/rpidmx512/t ... -baremetal).

I have found SPI timing issue with the Model 2. See viewtopic.php?p=914724#p914724 When you experience the same, then please update that append. Many thanks in advance.

- Arjan
http://www.raspberrypi-dmx.org/
Open Source DMX/RDM/MIDI/OSC/Art-Net/sACN solutions

cal-linux
Posts: 3
Joined: Thu Feb 25, 2016 4:44 pm

Re: Faster SPI

Mon Feb 29, 2016 5:07 pm

Joan: (or anyone else that would know the answer!)

I'm a bit uncertain about the 3-wire vs. 4-wire transfers in your code and also the channel selection.

As I understand from the documentation (https://www.raspberrypi.org/documentati ... /README.md),
the Pi only has SPI0 available on the header --- does that correspond to "channel 0" in your code?
In your mcp3202 test, you call it passing channel = 1. What am I missing?

Also, for the 3- vs. 4- wire --- in your code, you pass msglen for cnt4w and 0 for cnt3w, meaning
that you're requesting 4-wire transfers. The docs (link above) indicates that:

"In Standard SPI master mode the peripheral implements the standard 3 wire serial protocol
(SCLK, MOSI and MISO)".

The other modes are 2-wire and low-speed serial interface.

Could someone shed some light on this?

BTW, I could merge the relevant sections of your code into my bare-metal code based on the
Valvers tutorial --- I'm trying to do SPI transfers at perfectly regular 1-microsecond intervals.
It compiles, but I'm now puzzled by the above details.

Thanks,
Cal-linux
--

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Mon Feb 29, 2016 5:32 pm

cal-linux wrote:Joan: (or anyone else that would know the answer!)

I'm a bit uncertain about the 3-wire vs. 4-wire transfers in your code and also the channel selection.

As I understand from the documentation (https://www.raspberrypi.org/documentati ... /README.md),
the Pi only has SPI0 available on the header --- does that correspond to "channel 0" in your code?
In your mcp3202 test, you call it passing channel = 1. What am I missing?

Also, for the 3- vs. 4- wire --- in your code, you pass msglen for cnt4w and 0 for cnt3w, meaning
that you're requesting 4-wire transfers. The docs (link above) indicates that:

"In Standard SPI master mode the peripheral implements the standard 3 wire serial protocol
(SCLK, MOSI and MISO)".

The other modes are 2-wire and low-speed serial interface.

Could someone shed some light on this?

BTW, I could merge the relevant sections of your code into my bare-metal code based on the
Valvers tutorial --- I'm trying to do SPI transfers at perfectly regular 1-microsecond intervals.
It compiles, but I'm now puzzled by the above details.

Thanks,
Cal-linux
--
I mean the following by 3-wire and 4-wire. Others may mean something else - I don't think there is an agreed definition.

4-wire is normal SPI where the device has separate pins for serial data in and serial data out (plus clock and slave select for a total of 4 wires needed).

3-wire is for those SPI devices which have a single pin for serial data in and serial data out. The pin stays in serial in mode for a certain number of bytes and then switches to serial data out mode.

The Pi has two usable SPI engines.

The standard SPI device (used by the standard Linux driver) has two slave select lines. The slave select line is used to select the device to talk with.

The auxiliary SPI device (not currently supported by the standard Linux driver) has three slave select lines.

If you are cutting code the best source would be pigpio.c.

wietjes
Posts: 14
Joined: Tue Feb 16, 2016 7:13 pm

Re: Faster SPI

Thu Mar 24, 2016 4:51 pm

Hi joan ,
I was wondering if you could tell me what this piece of code does:

Code: Select all

int read_mcp3202(
...
   txBuf[0] = 1;
   if (adc_channel == 0) txBuf[1] = 0x80; else txBuf[1] = 0xC0;
   txBuf[2] = 0;

   spiXfer(speed, spi_channel, mode, 0, txBuf, rxBuf, msglen, 0); /* SPI xfer */

   return ((rxBuf[1]&0x0F)<<8) + rxBuf[2];
}
As I understand it, the txBuf are the bytes you send to the adc to start the reading-operating?
but then what does this?

Code: Select all

return ((rxBuf[1]&0x0F)<<8) + rxBuf[2];
kind regards

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Thu Mar 24, 2016 5:59 pm

wietjes wrote: ...
Hi joan ,
I was wondering if you could tell me what this piece of code does:
...
Like a few other of the MCP3XXX ADCs the command starts with the first high bit after slave select becomes active (low). The following three bits determine the command. 0 bits before the first bit are ignored by the device.

See if you can make sense of the three bytes sent/received from the following diagram and the specification sheets.

Code: Select all

   Byte 1                  |  Byte 2                   |  Byte 3
    1  2  3  4  5  6  7  8 |  1  2  3  4   5   6  7  8 |  1  2  3  4  5  6  7  8
CMD .  .  .  .  .  .  . SB | SD OS MS NA B11 B10 B9 B8 | B7 B6 B5 B4 B3 B2 B1 B0
18  0  0  0  0  0  0  0  1 |  1  0  0  .   .   .  .  . |  .  .  .  .  .  .  .  .
1C  0  0  0  0  0  0  0  1 |  1  1  0  .   .   .  .  . |  .  .  .  .  .  .  .  .
RX  .  .  .  .  .  .  .  . |  .  .  .  .   X   X  X  X |  X  X  X  X  X  X  X  X

wietjes
Posts: 14
Joined: Tue Feb 16, 2016 7:13 pm

Re: Faster SPI

Fri Mar 25, 2016 12:28 pm

So i'm trying to do apply your code for the mcp3008 but I can't make it work..
this is my edit:

Code: Select all

int read_mcp3008(unsigned speed,unsigned spi_channel,unsigned mode,unsigned adc_channel){
   const int msglen = 3;

   char txBuf[msglen];
   char rxBuf[msglen];
   int a2dVal;

   txBuf[0] = 1;
   txBuf[1] = 0x80; 
   txBuf[2] = 0;

   /* speed, channel, mode, cspol, *txBuf,*rxBuf,cnt4w,cnt3w */

   spiXfer(speed, spi_channel, mode, 0, txBuf, rxBuf, msglen, 0); /* SPI xfer */

   a2dVal = 0;
   a2dVal = (rxBuf[1]<< 8) & 0b1100000000; //merge rxBuf[1] & rxBuf[2] to get result
   a2dVal |= (rxBuf[2] & 0xff);

   return a2dVal;

   //return ((rxBuf[1]&0x0F)<<8) + rxBuf[2];
}

int mcp3008(int argc, char *argv[]){
   int i;
   int speed, iters, maxsps, pf;
   unsigned divider;
   unsigned reading[1024];
   unsigned val;
   double start, duration;
   uint32_t nt, micros;
   int tdiff;

   if (argc >= 4){

      speed  = atoi(argv[1]);
      iters  = atoi(argv[2]);
      maxsps = atoi(argv[3]);
      micros = 1000000 / maxsps;
      if (argc > 4) pf = 1; else pf = 0;}

   else{
      fprintf(stderr, "error");
      return 1;}

   for (i=0; i<1024; i++) reading[i]= 0;

   divider = 100000000/speed;
   if (divider % 1) divider++;

      val = read_mcp3008(speed, 1, 0, 0);

      if (pf) printf("%u\n", val);

   spiInit(); /* save old SPI settings, initialise */

   printf ("selected speed=%d (set=%u) iters=%d xfer=3 mingap=%d\n",
      speed, 100000000/divider, iters, micros);

   start = time_time();

   for (i=0; i<iters; i++){
      nt = gpioTick() + micros;
      val = read_mcp3008(speed, 1, 0, 0);

      if (pf) printf("%u\n", val);

      reading[(val/10)*10]++;

      do /* rate limit */
      {
         tdiff = nt - gpioTick();
      } while (tdiff > 0);
   }

   duration = time_time()  - start;

   spiTerm(); /* restore old SPI settings */

   for (i=0; i<1024; i++){
      if (reading[i]) printf("%d %d\n", i, reading[i]);
   }

   printf("%.0f sps over %.1f seconds\n", ((float)iters)/(duration), duration);

   return 0;
}

main(int argc, char *argv[])
{
   if (gpioInitialise() < 0) return 1;

   return mcp3008(argc, argv);
}
i changed the bytes you need to send to start the communication and the buffersize (from 4096 to 1024)

I try to make it run with

Code: Select all

sudo ./spi 1000000 1000000 75000
But the only output i get is:

Code: Select all

selected speed=1000000 (set=1000000) iters=1000000 xfer=3 mingap=10
really don't know what I'm missing here...

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Fri Mar 25, 2016 6:44 pm

I suggest you add some more printfs into the code to see what is going on. Perhaps after each ADC read to check the results look okay.

wietjes
Posts: 14
Joined: Tue Feb 16, 2016 7:13 pm

Re: Faster SPI

Mon Apr 11, 2016 11:25 am

Hi joan,

I've put some prints in the code to see where it stalls.
Looks like the SpiXfer function doesn't react... the print I put under the function doesn't get printed.
This is what it looks like..

Code: Select all

spiXfer(speed, 0, mode, 0, txBuf, rxBuf, msglen, 0);
So I'm trying to read values from a MCP3008. The 4-wire option is choosen but I wonder why this needs the value of 3?
As msglen is defined as

Code: Select all

const int msglen = 3;

User avatar
joan
Posts: 14010
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Faster SPI

Mon Apr 11, 2016 11:49 am

@wietjes

I've lost track of what you are trying to do.

If you are just trying to read an MCP3008 use the ordinary library function spiXfer.

The code to do what was mentioned at the start of this thread is now part of the main library. Just get a handle with spiOpen and then call spiXfer as needed.

Return to “Interfacing (DSI, CSI, I2C, etc.)”