petzval
Posts: 11
Joined: Sat Aug 10, 2013 12:15 pm

Accurate timing for real time control

Sat Aug 10, 2013 1:42 pm

Real time control with a Pi faces two problems.
Firstly, timing done via software loops involves endless fussing over the exact clock cycle count. This code solves that problem by using the processor's free-running 1MHz timer, allowing delays of a chosen number of microseconds to be set up and executed with two lines of C code.
Secondly, interrupts are constantly pulling the rug out from under your feet by going off to do something else. This can sometimes take 2ms which scuppers any attempt at microsecond-scale control. This code disables interrupts and ensures a timing precision of one microsecond, or 0.1 microsecond with proper synchronisation.
As a demonstration, the attached C program sends a 50kHz signal to a GPIO pin with a jitter of 0.1 microseconds.

[attachment]

Code: Select all

/********** ACCURATE TIMER for REAL TIME CONTROL ***

This C program illustrates accurate timing on a
Raspberry Pi by sending a 50kHz signal to a GPIO pin
with a jitter of about 0.1 microseconds. It uses the
processor's 1MHz timer and disables interrupts.
It includes GPIO setup and read/write code.

Compiled from console with gcc under the standard
Debian distribution.
Tested with a keyboard and HDMI monitor attached,
and X Windows not started.

**************************************************/


/*********** TIMER CODE example *******

unsigned int timend;

setup()                  // initialise system
                         // call only once

interrupts(0);           // disable interrupts

timend = *timer + 200;   // Set up 200 microsecond delay
                         // Maximum possible delay
                         // is 7FFFFFF or about 35 minutes

while((((*timer)-timend) & 0x80000000) != 0);  // delay loop

                     // This works even if *timer
                     // overflows to zero during the delay,
                     // or if the while test misses the exact
                     // termination when (*timer-timend) == 0.
                     // Jitter in delay about 1 microsceond.
                     // Can be reduced to about 0.1 microsecond
                     // by synchronising the timend set
                     // instruction to a change in *timer

                     // if interrupts are not disabled
                     // the delay can occasionally be
                     // 2ms (or more) longer than requested
                     // and is routinely out by up to 0.1ms

interrupts(1);       // re-enable interrupts

*************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>

#define GPIO_BASE  0x20200000
#define TIMER_BASE 0x20003000
#define INT_BASE 0x2000B000

volatile unsigned *gpio,*gpset,*gpclr,*gpin,*timer,*intrupt;

/******************** GPIO read/write *******************/
  // outputs  CLK = GPIO 15 = connector pin 10
  //          DAT = GPIO 14 = connector pin 8
  // code example
  //    CLKHI;
#define CLKHI *gpset = (1 << 15)  // GPIO 15
#define CLKLO *gpclr = (1 << 15)
#define DATHI *gpset = (1 << 14)  // GPIO 14
#define DATLO *gpclr = (1 << 14)
  // inputs   P3  = GPIO 3 = connector pin 5 (Rev 2 board)
  //          P2  = GPIO 2 = connector pin 3 (Rev 2 board)
  //          ESC = GPIO 18 = connector pin 12
  // code examples
  //   if(P2IN == 0)
  //   if(P2IN != 0)
  //   n = P2INBIT;  //  0 or 1
#define ESCIN (*gpin & (1 << 18))   // GPIO 18
#define P2IN (*gpin & (1 << 2))    // GPIO 2
#define P3IN (*gpin & (1 << 3)     // GPIO 3
#define P2INBIT ((*gpin >> 2) & 1)  // GPIO 2
#define P3INBIT ((*gpin >> 3) & 1)  // GPIO 3
/******************* END GPIO ****************/

int setup(void);
int interrupts(int flag);

main()
  {
  int n,getout;
  unsigned int timend;

  sleep(1);    // 1 second delay
               // When the program starts, the interrupt
               // system may still be dealing with the
               // last Enter keystroke. This gives it
               // time to finish.

  setup();     // setup GPIO, timer and interrupt pointers

  interrupts(0);    // Disable interrupts to ensure
                    // accurate timing.
                    // Re-enable via interrupts(1) as
                    // soon as accurate timing is no
                    // longer needed.

        // screen output, keyboard input (and who
        // knows what else) stop working until
        // interrupts are re-enabled

  // 50kHz signal to CLK output = GPIO 15 connector pin 10
  // 1000000 cycles = 20 seconds
  // checks ESC input pin = GPIO 18 connector pin 12
  // if lo - loop terminates

  getout = 0;
  timend = *timer + 10;   // set up 10us delay from
                          // current timer value
  for(n = 0 ;  n < 1000000 && getout == 0 ; ++n)
    {
                          //  delay to timend
    while( (((*timer)-timend) & 0x80000000) != 0);

    CLKHI;           // output GPIO 15 hi

                     // check input GPIO 18 pin
                     // which is pulled hi by setup()
                     // exit loop if lo
    if(ESCIN == 0)
      getout = 1;
                         // 10us delay
    timend += 10;
    while( (((*timer)-timend) & 0x80000000) != 0);

    CLKLO;               // output GPIO 15 lo

    timend += 10;        // 10us delay at start of next loop
    }

  interrupts(1);         // re-enable interrupts

  return;
  }

/******************** INTERRUPTS *************

Is this safe?
Dunno, but it works

interrupts(0)   disable interrupts
interrupts(1)   re-enable interrupts

return 1 = OK
       0 = error with message print

Uses intrupt pointer set by setup()
Does not disable FIQ which seems to
cause a system crash
Avoid calling immediately after keyboard input
or key strokes will not be dealt with properly

*******************************************/

int interrupts(int flag)
  {
  static unsigned int sav132 = 0;
  static unsigned int sav133 = 0;
  static unsigned int sav134 = 0;

  if(flag == 0)    // disable
    {
    if(sav132 != 0)
      {
      // Interrupts already disabled so avoid printf
      return(0);
      }

    if( (*(intrupt+128) | *(intrupt+129) | *(intrupt+130)) != 0)
      {
      printf("Pending interrupts\n");  // may be OK but probably
      return(0);                       // better to wait for the
      }                                // pending interrupts to
                                       // clear

    sav134 = *(intrupt+134);
    *(intrupt+137) = sav134;
    sav132 = *(intrupt+132);  // save current interrupts
    *(intrupt+135) = sav132;  // disable active interrupts
    sav133 = *(intrupt+133);
    *(intrupt+136) = sav133;
    }
  else            // flag = 1 enable
    {
    if(sav132 == 0)
      {
      printf("Interrupts not disabled\n");
      return(0);
      }

    *(intrupt+132) = sav132;    // restore saved interrupts
    *(intrupt+133) = sav133;
    *(intrupt+134) = sav134;
    sav132 = 0;                 // indicates interrupts enabled
    }
  return(1);
  }

/***************** SETUP ****************
Sets up five GPIO pins as described in comments
Sets timer and interrupt pointers for future use
Does not disable interrupts
return 1 = OK
       0 = error with message print
************************************/

int setup()
  {
  int memfd;
  unsigned int timend;
  void *gpio_map,*timer_map,*int_map;

  memfd = open("/dev/mem",O_RDWR|O_SYNC);
  if(memfd < 0)
    {
    printf("Mem open error\n");
    return(0);
    }

  gpio_map = mmap(NULL,4096,PROT_READ|PROT_WRITE,
                  MAP_SHARED,memfd,GPIO_BASE);

  timer_map = mmap(NULL,4096,PROT_READ|PROT_WRITE,
                  MAP_SHARED,memfd,TIMER_BASE);

  int_map = mmap(NULL,4096,PROT_READ|PROT_WRITE,
                  MAP_SHARED,memfd,INT_BASE);

  close(memfd);

  if(gpio_map == MAP_FAILED ||
     timer_map == MAP_FAILED ||
     int_map == MAP_FAILED)
    {
    printf("Map failed\n");
    return(0);
    }
              // interrupt pointer
  intrupt = (volatile unsigned *)int_map;
              // timer pointer
  timer = (volatile unsigned *)timer_map;
  ++timer;    // timer lo 4 bytes
              // timer hi 4 bytes available via *(timer+1)

              // GPIO pointers
  gpio = (volatile unsigned *)gpio_map;
  gpset = gpio + 7;     // set bit register offset 28
  gpclr = gpio + 10;    // clr bit register
  gpin = gpio + 13;     // read all bits register

      // setup  GPIO 2/3 = inputs    have pull ups on board
      //        control reg = gpio + 0 = pin/10
      //        GPIO 2 shift 3 bits by 6 = (pin rem 10) * 3
      //        GPIO 3 shift 3 bits by 9 = (pin rem 10) * 3

  *gpio &= ~(7 << 6);   // GPIO 2  3 bits = 000 input
  *gpio &= ~(7 << 9);   // GPIO 3  3 bits = 000 input

     // setup GPIO 18 = input
  *(gpio+1) &= ~(7 << 24);  // GPIO 18 input
     // enable pull up on GPIO 18
  *(gpio+37) = 2;           // PUD = 2  pull up
                            //     = 0  disable pull up/down
                            //     = 1  pull down
  timend = *timer+2;        // 2us delay
  while( (((*timer)-timend) & 0x80000000) != 0);
  *(gpio+38) = (1 << 18);   // PUDCLK bit set clocks PUD=2 to GPIO 18
  timend = *timer+2;        // 2us delay
  while( (((*timer)-timend) & 0x80000000) != 0);
  *(gpio+37) = 0;           // zero PUD
  *(gpio+38) = 0;           // zero PUDCLK
                            // finished pull up enable

     //      GPIO 14/15 = outputs
     //      control reg = gpio + 1 = pin/10
     //      GPIO 14 shift 3 bits by 12 = (pin rem 10) * 3
     //      GPIO 15 shift 3 bits by 15 = (pin rem 10) * 3

  *(gpio+1) &= ~(7 << 12);  // GPIO 14 zero 3 bits
  *(gpio+1) |= (1 << 12);   // 3 bits = 001 output

  *(gpio+1) &= ~(7 << 15);  // GPIO 15 zero 3 bits
  *(gpio+1) |= (1 << 15);   // 3 bits = 001 output

  return(1);
  }

/**************** PULL UPS *********

pull up register  PUD = gpio+37
clock register PUDCLK = gpio+38

1. set PUD =  0 disable pull up/down
              1 enable pull down
              2 enable pull up
              3 reserved
   *(gpio+37) = 2  to pull up

2. wait 150 cycles

3. set bit of GPIO pin in PUDCLK
   so for GPIO 3    *(gpio+38) = 8
   to clock PUD into GPIO 3 only

4. wait 150 cycles

5. write 0 to PUD

6. write 0 to PUDCLK

************ END ************************/
[/attachment]

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

Re: Accurate timing for real time control

Sat Aug 10, 2013 2:46 pm

I've been playing around with generating arbitrary timed waveforms. I've used it to 'bit-bang' serial data on multiple user gpios simultaneously.

This does a similar thing to your code but uses DMA and interrupts are not disabled.

Code: Select all

#include <stdio.h>

#include <pigpio.h>

#define LED 4

gpioPulse_t pulse[2]; /* only need two pulses for a square wave */

int main(int argc, char *argv[])
{
   int secs=60, us = 1000000;

   if (argc>1) us   = atoi(argv[1]); /* square wave micros */
   if (argc>2) secs = atoi(argv[2]); /* program run seconds */

   if (us<2) us = 2; /* minimum of 2 micros per pulse */

   if ((secs<1) || (secs>3600)) secs = 3600;

   if (gpioInitialise()<0) return 1;

   gpioSetMode(LED, PI_OUTPUT);

   pulse[0].gpioOn = (1<<LED); /* high */
   pulse[0].gpioOff = 0;
   pulse[0].usDelay = us;

   pulse[1].gpioOn = 0;
   pulse[1].gpioOff = (1<<LED); /* low */
   pulse[1].usDelay = us;

   gpioWaveClear();

   gpioWaveAddGeneric(2, pulse);

   gpioWaveTxStart(PI_WAVE_MODE_REPEAT);

   sleep(secs);

   gpioWaveTxStop();

   gpioTerminate();
}

mrmsbarnes
Posts: 16
Joined: Thu Jul 31, 2014 11:27 pm

Re: Accurate timing for real time control

Fri Aug 01, 2014 6:07 am

The pigpio library is certainly a marvel and a huge convenience, and if you stick to gpioRead (no writes) you can even get nearly reliable accuracy (repeatable performance). If you use gpioWrite, however, in even a simple loop, not as in the fast loop in minimal_gpio.c, you will see a very different performance. Timing this, sometimes I get a duration of 1.5 seconds, sometimes around .8 seconds. The strange thing is the duration is either at one end or the other. If you are driving a LED in the fast loop, you will see either a solid or flickering light.

I think the author's point is, to achieve accurate timing you pretty much need to hold off interrupts for the period where you need accuracy.

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

Re: Accurate timing for real time control

Fri Aug 01, 2014 9:33 am

mrmsbarnes wrote:The pigpio library is certainly a marvel and a huge convenience, and if you stick to gpioRead (no writes) you can even get nearly reliable accuracy (repeatable performance). If you use gpioWrite, however, in even a simple loop, not as in the fast loop in minimal_gpio.c, you will see a very different performance. Timing this, sometimes I get a duration of 1.5 seconds, sometimes around .8 seconds. The strange thing is the duration is either at one end or the other. If you are driving a LED in the fast loop, you will see either a solid or flickering light.

I think the author's point is, to achieve accurate timing you pretty much need to hold off interrupts for the period where you need accuracy.
That seems an odd result. Could you post the code you used? That behaviour suggests a pigpio bug.

mrmsbarnes
Posts: 16
Joined: Thu Jul 31, 2014 11:27 pm

Re: Accurate timing for real time control

Fri Aug 01, 2014 10:48 pm

Regrettably I cannot resurrect the exact code nor breadboard setup I was using. However, I can still provide two samples (if I can post code correctly) that show the problem I think petzval is trying to overcome. The first sample is a modification of joan's code. Instead of sleeping while the square wave is generated, this sample counts gpio transitions. For the delays set up, there should be about 16000 transitions in 2 seconds. There is added overhead because the main thread is periodically checking transitions instead of just doing a sleep. The overhead varies between 12 and 24 percent. Note sure exactly why such a variance.

What I'm fairly certain of is that interrupts affect timing. While this sample executes, if you swirl the mouse rapidly, maybe cause some windows to repaint, you can easily get the 16000 transitions to take over 4 seconds. Meaning the pulse widths are varying. This is because the kernel is busy servicing non-gpio interrupts on a non-multi-core processor.

Code: Select all

#include <stdio.h>
#include <pigpio.h>

#define LED 4

gpioPulse_t pulse[2]; /* only need two pulses for a square wave */

int main(int argc, char *argv[])
{
   int secs=2, us = 125; //-// defaults I wanted: 2 sec duration and 250us or 4KHz cycle
   unsigned start, stop; //-// added
   int last, now, edges; //-// added

   if (argc>1) us   = atoi(argv[1]); /* square wave micros */
   if (argc>2) secs = atoi(argv[2]); /* program run seconds */
   if (us<2) us = 2; /* minimum of 2 micros per pulse */
   if ((secs<1) || (secs>3600)) secs = 3600;

   if (gpioInitialise()<0) return 1;

   gpioSetMode(LED, PI_OUTPUT);

   pulse[0].gpioOn = (1<<LED); /* high */
   pulse[0].gpioOff = 0;
   pulse[0].usDelay = us;

   pulse[1].gpioOn = 0;
   pulse[1].gpioOff = (1<<LED); /* low */
   pulse[1].usDelay = us;

   gpioWaveClear();
   gpioWaveAddGeneric(2, pulse);
   
   last = 1; edges = 0; //-// init for counting
   start = gpioTick(); //-//
   gpioWaveTxStart(PI_WAVE_MODE_REPEAT);
   //-// sleep(secs); Why sleep?  Let's see how long 16K edges (8K cycles) takes
   while (edges != 16000) { //-// in place of sleep
      now = gpioRead(LED);
      if (last != now) {
         edges++;
         last = now;
      }
      gpioSleep(0, 0, 25); //sleep relative 25us.  Should have plenty of chances to see transitions
   }   
   gpioWaveTxStop();
   stop = gpioTick(); //-//
   
   printf("start to stop us: %d\n", stop - start);
   gpioTerminate();
}
The second attachment is a modification of minimal_gpio.c from http://abyz.co.uk/rpi/pigpio/code/minimal_gpio.zip. This does NOT use the pigpio library but uses its own direct memory accesses. Here again, swirling the mouse affects timing.

Code: Select all

/*
   gcc -o minimal_gpio minimal_gpio.c
   sudo ./minimal_gpio
*/

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

#define CLK_BASE   0x20101000
#define DMA_BASE   0x20007000
#define DMA15_BASE 0x20E05000
#define GPIO_BASE  0x20200000
#define PCM_BASE   0x20203000
#define PWM_BASE   0x2020C000
#define SPI0_BASE  0x20204000
#define SYST_BASE  0x20003000
#define UART0_BASE 0x20201000
#define UART1_BASE 0x20215000

#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 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

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

static volatile uint32_t  *gpioReg = MAP_FAILED;
static volatile uint32_t  *systReg = MAP_FAILED;

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

/* 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]; }

uint32_t tickArray [10001]; // added array for tick readings

/* 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);

   close(fd);

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

main()
{
   int i, diff, start, stop;

   if (gpioInitialise() < 0) return 1;

   //for (i=0; i<54; i++)
   //{
   //   printf("gpio=%d tick=%u mode=%d level=%d\n",
   //      i, gpioTick(), gpioGetMode(i), gpioRead(i));
   //}

   gpioSetMode(22, 1);
   
   //for (i=0; i<50; i++)
   //{
   //   gpioWrite(22, 1);
   //   usleep(50000);
   //   gpioWrite(22, 0);
   //   usleep(50000);
   //}

   printf("starting as fast as you can loop.\n");
   sleep(1);
   start = gpioTick();

   // Try two cases:
   // 1. Simply run compiled executable. E.g: 'sudo ./minimal_gpio'
   // 2. Same as 1. but swirl mouse around while the following fast loop executes
   for (i=0; i<1000000; i++)
   {
      if (i < 10002)
        tickArray[i] = gpioTick();

      gpioWrite(22, 1);
      // Try a version where the next line is commented out.
      gpioRead(22); // Delay needed for correct reading.  Next...
      if (gpioRead(22) != 1)
      	printf("late switch ON\n");

      gpioWrite(22, 0);
      gpioRead(22); // Needed delay.  sleep or other could also be used
      if (gpioRead(22) != 0)
      	printf("late switch OFF\n");
   }
   stop = gpioTick();

   // check loop time for first 10000 iterations   
   for (i=0; i<10000; i++)
   {
      if ((diff = tickArray[i+1] - tickArray[i]) > 40) // 'too long' threshhold, arbitrary
         printf("%d\n", diff);
   }
   printf("start to stop ticks: %d\n", stop - start);
}
Now I guess I have to prove to myself petzval's solution for short periods works. I DO need reliable timing for an application under development.

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

Re: Accurate timing for real time control

Sat Aug 02, 2014 7:53 am

Is this under X? I believe the X cursor has been optimised to use DMA which may be the source of the perturbations. Using a different DMA channel or disabling the DMA cursor or resisting the temptation of swirling the cursor spring to mind as things to try. Given that the alternative is to disable interrupts I can't see much of a difference.

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

Re: Accurate timing for real time control

Sat Aug 02, 2014 8:17 am

I've just connected a screen and a mouse to a B+.

I've run up piscope on a laptop looking at the B+ which has 800Hz equal dutycycle PWM on gpios 4 and 16 and a view size of 220ms. I'd expect to see any problems but swirling the cursor has no visible effect.

Could you confirm you are using callbacks? http://abyz.co.uk/rpi/pigpio/cif.html#gpioSetAlertFunc or http://abyz.co.uk/rpi/pigpio/cif.html#g ... lertFuncEx. gpioRead/gpioWrite will be susceptible to Linux scheduling.

mrmsbarnes
Posts: 16
Joined: Thu Jul 31, 2014 11:27 pm

Re: Accurate timing for real time control

Sun Aug 03, 2014 12:39 am

Regards the two samples I submitted. They were both run under X (I need my UI, man). In the second example callbacks are not involved. The loop only uses gpioTick and the gpioRead's and gpioWrite's.

I believe joan is correct that the gpio reads and writes are susceptible to interrupts. That's the problem. My searching says real real-time on the Pi isn't possible. So I'm still searching for the 'most accurate' way of timing and responding to ticks of a 4KHz clock over a period of about 150ms. Probably the free-running 1MHz clock will be involved. I'm still looking at disabling interrupts for that period. I just don't know the best answer yet.

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

Re: Accurate timing for real time control

Sun Aug 03, 2014 7:08 am

mrmsbarnes wrote:Regards the two samples I submitted. They were both run under X (I need my UI, man). In the second example callbacks are not involved. The loop only uses gpioTick and the gpioRead's and gpioWrite's.

I believe joan is correct that the gpio reads and writes are susceptible to interrupts. That's the problem. My searching says real real-time on the Pi isn't possible. So I'm still searching for the 'most accurate' way of timing and responding to ticks of a 4KHz clock over a period of about 150ms. Probably the free-running 1MHz clock will be involved. I'm still looking at disabling interrupts for that period. I just don't know the best answer yet.
GUIs won't work with interrupts disabled.

Timing a 4KHz clock is no problem. What response do the ticks require over the 150ms period?

mrmsbarnes
Posts: 16
Joined: Thu Jul 31, 2014 11:27 pm

Re: Accurate timing for real time control

Fri Aug 08, 2014 8:00 pm

Now I believe gpioTick() from pigpio's minimal_gpio.c (thanks joan) is the hammer that hits all nails.

Code: Select all

#include <sys/fcntl.h> /* O_RDWR, O_SYNC */
#include <sys/mman.h>  /* mmap and options */
#define SYST_BASE  0x20003000
#define SYST_LEN  0x1C
#define SYST_CLO 1
static volatile uint32_t *syst_reg;

void *map_peripheral(uint32_t base, uint32_t len) { /* actually this mmap func is sorta from pi-blaster */
    int fd = open("/dev/mem", O_RDWR | O_SYNC); /* real func handles open failure */
    void * vaddr;
    vaddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, base);
    close(fd);
    return vaddr;
}

syst_reg = map_peripheral(SYST_BASE, SYST_LEN); /* set one time*/

uint32_t gpioTick(void) { return syst_reg[SYST_CLO]; } /* use the hammer anytime you want to know tick count */
For timing, I found Hirst/Sarlandie's servoblaster/pi-blaster and see that the hard PWM on GPIO 4 is impervious to interrupts (mouse swirling).
Unfortunately this method appears to have limits. In a modified version of pi-blaster with only GPIO 4 known, I used the hammer in place of nanosleep in udelay() since Gordon says nanosleep can have horrific system delays.
I found these were pretty much the limit:

Code: Select all

#define CYCLE_TIME_US		100
#define SAMPLE_US		1
#define NUM_SAMPLES		1000
This gives a stable 1KHz clock on GPIO 4 when this pi-blaster daemon is running and I enter

echo "4=0.5">/dev/pi-blaster

At least I have a stable way to start something every millisecond. Since I'm convinced I'll never have a predictable way of reading GPIO pins, I'm using the hammer for microsecond timestamps and just living with the variance.

And, no, I still haven't tested petzval's original solution of holding off interrupts for short periods. His 50kHz would definitely cover my 4kHz goal.

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

Re: Accurate timing for real time control

Fri Aug 08, 2014 8:18 pm

If you want to know the time of events accurately from C you have to use pigpio callbacks set up by either http://abyz.co.uk/rpi/pigpio/cif.html#gpioSetAlertFunc or http://abyz.co.uk/rpi/pigpio/cif.html#g ... lertFuncEx. Everything else on the input side is subject to the vagaries of Linux scheduling.

If you clarify what you are trying to achieve I may be able to suggest a solution.

mrmsbarnes
Posts: 16
Joined: Thu Jul 31, 2014 11:27 pm

Returning to the scene of the crime.

Tue Sep 09, 2014 8:32 pm

I found pi-blaster was overkill for what I was looking for (accurate clock). I just wanted to post this link:
http://panteltje.com/pub/
If you search for freq_pi.c or freq_pi-0.2.c on the page, Jan Panteltje has provide a c-level gpio clock generator that works well and covers a large range of frequencies. There are probably better links to this source. (Thank you, Jan)

dimonic
Posts: 41
Joined: Fri Jun 08, 2012 9:08 pm

Re: Accurate timing for real time control

Wed Feb 04, 2015 3:29 am

I know this is an old post, but there appears to be a missing semi-colon in the first example after the initial call to setup().

SteveA
Posts: 29
Joined: Sat Mar 14, 2015 11:18 am
Location: South Yorkshire, England

Re: Accurate timing for real time control

Wed Apr 08, 2015 5:13 pm

Hello
I know it is an old post, but the petzval's code was an excellent way of reading the 1MHz system tick counter whilst disabling interrupts in order to get accurate reads. All worked fine until I changed to using the RPI2. I have altered the base address from 0x20000000 to 0x3f000000 (due to the BCM2386), with the relevant offsets and can still read the tick counter fine.

My problem is that I am not sure that the interrupts are being disabled properly as there is jitter on the readings, which did not occur with my previous RPIs (version 1)

I can looked at petzval's code to see how the interrupts were disabled but cannot find any documentation as to which registers are being used in order that I can compare the bcm2385 data with the bcm2386 data.

If anyone could point me in the right direction (in c) as to how to stop all interrupts on the RPI2 or give me an insight into how petzval found the registers which to use, then I would be grateful.

Thanks

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

Re: Accurate timing for real time control

Wed Apr 08, 2015 5:31 pm

The registers will be the same. See page 112 of the peripherals document.

I'm not sure (i.e. haven't read) if http://www.raspberrypi.org/documentatio ... rev3.4.pdf changes anything as far as using those registers.

SteveA
Posts: 29
Joined: Sat Mar 14, 2015 11:18 am
Location: South Yorkshire, England

Re: Accurate timing for real time control

Thu Apr 16, 2015 9:46 am

Thank you Joan for showing me the relevant documentation. I had seen elsewhere that the offset addresses to be added to the ARM interrupt register, 0x7E00B00, started at 0x200; for example 0x210 is Enable IRQs 1, and so on.

This is what is causing me confusion because the code used by petzval uses offset addresses (decimal) 128, 129, 130, etc. I am having a problem in comparing the documentation with that code. It will be due to a misunderstanding on my part and if you can see the relationship between the 128, 129, etc, and the registers, please point me towards it.

Thanks again

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

Re: Accurate timing for real time control

Thu Apr 16, 2015 9:58 am

SteveA wrote:Thank you Joan for showing me the relevant documentation. I had seen elsewhere that the offset addresses to be added to the ARM interrupt register, 0x7E00B00, started at 0x200; for example 0x210 is Enable IRQs 1, and so on.

This is what is causing me confusion because the code used by petzval uses offset addresses (decimal) 128, 129, 130, etc. I am having a problem in comparing the documentation with that code. It will be due to a misunderstanding on my part and if you can see the relationship between the 128, 129, etc, and the registers, please point me towards it.

Thanks again
It's just a feature of C. The base pointer to the area is an unsigned, a four byte quantity. A property of C pointers is that adding x to the pointer actually points to x*sizeof(type being pointed to). So decimal 128 points to byte 512 or 0x200 in the table.

SteveA
Posts: 29
Joined: Sat Mar 14, 2015 11:18 am
Location: South Yorkshire, England

Re: Accurate timing for real time control

Thu Apr 16, 2015 10:04 am

Joan, you're a star... I said it was my misunderstanding.

Thanks

diracasaurus
Posts: 1
Joined: Wed Apr 22, 2015 2:48 pm

Re: Accurate timing for real time control

Mon Apr 27, 2015 6:15 pm

I've successfully used this for a project I'm working on, thank you for that. I was wondering if you tried using this with the Rapsberry Pi 2? I think the GPIO pins are moved around. Thanks!

LightEmittingDude
Posts: 5
Joined: Thu Apr 18, 2013 10:57 pm

Re: Accurate timing for real time control

Fri Apr 01, 2016 8:03 pm

Hi all.

I need to generate an accurate (+/- 5 Hz) 77.5kHz square wave.

Previously I have used the hardware PWM, via the WiringPi python module, but in this case I can't get close enough by dividing the 19.2MHz oscillator by a fixed factor.

One method I'm considering is rapidly alternating between a divisor of 248 and (every fourth cycle) 247. Thus getting me an effective divide-by-247.75 which is close enough.

What are your thoughts on this method? Is it feasible? What is the fastest rate at which I could toggle the divisor, and could this process be interrupt-driven? Or is there just a better approach that I should be looking at?

I look forward to your comments.

Simon

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

Re: Accurate timing for real time control

Fri Apr 01, 2016 10:12 pm

The powers that be are mucking about with the clocks but 77519.37 is what pigs HP should give. I'd have to check but the pigs HC command may give a value within 5 Hz.

If neither work you could possibly use pigpio waves to get arbitrarily close if you accept a little jitter.

LightEmittingDude
Posts: 5
Joined: Thu Apr 18, 2013 10:57 pm

Re: Accurate timing for real time control

Sat Apr 02, 2016 4:04 am

Flippin 'eck - there's a lot there - including some very interesting-looking examples. You're the author? Impressive. I will be exploring this with gusto.

Question: when you use HC to set the GPIO clock frequency, is this the 19.2MHz that I am familiar with? I was wondering where that came from.

Thanks,

S

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

Re: Accurate timing for real time control

Sat Apr 02, 2016 5:19 am

LightEmittingDude wrote:Flippin 'eck - there's a lot there - including some very interesting-looking examples. You're the author? Impressive. I will be exploring this with gusto.

Question: when you use HC to set the GPIO clock frequency, is this the 19.2MHz that I am familiar with? I was wondering where that came from.

Thanks,

S
They use the 500 MHz PLLD (from memory) rather than the 19.2 MHz crystal. There is a 1000 MHz PLLC (from memory). See http://abyz.co.uk/rpi/pigpio/examples.h ... inimal_clk

LightEmittingDude
Posts: 5
Joined: Thu Apr 18, 2013 10:57 pm

Re: Accurate timing for real time control

Tue Apr 05, 2016 9:35 pm

Just punching in

Code: Select all

pi1.hardware_PWM(18,77500,500000)
gives me, I think, 77,495.35 Hz which ought to be close enough (and you suggested that is as good as it could possibly get).
I arrived at this figure by looking at the output of

Code: Select all

pi1.get_real_PWM_range(18)
which is 3226. My scope confirms this within the limit of its 4-sig-fig frequency display.

The issue now is that using the same settings, the other frequency I want to generate (60kHz) is no longer bang on as it was before using WiringPi and the 19.2MHz crystal. Is there a user forum specifically for pigpio, or failing that, a concise overview of 'clock systems and their distribution' such as is typically found in Atmel microcontroller datasheets? I'm finding a big gap between my negligible knowledge of the Broadcom nuts-and-bolts and your very concise documentation.

Thanks,

Simon

cnlohr
Posts: 15
Joined: Fri Feb 12, 2016 10:34 pm

Re: Accurate timing for real time control

Tue Nov 06, 2018 5:16 am

Do we have _any_ shot at getting this for the Pi3? There is _SO MUCH_ that could be done if we could totally disable interrupts for brief periods of time to bang something out a GPIO with high detail. I am wracking around but even after fixing the peripheral offsets, the timing still goes out by a microsecond or two.

This is what I'm using...

Code: Select all

#define BCM2708_PERI_BASE       0x3F000000
#define INT_BASE (BCM2708_PERI_BASE+0x000B000)
Additionally, I've tried rewriting some of it to no improvement, but even when I look newer this code "looks" like it "should" work on newer procs...

*EDIT* So, the code in the original post _does_ disable interrupts, like when operating in a critical section, network packets pause... but... I am still getting random freezes. I think there may be interrupts hiding somewhere else.

Return to “Advanced users”