I've had no success running a 1 millisecond servo loop on Pandaboard, 
Beaglebone, and Raspberry Pi2.   Attached is a POSIX real-time test program 
I wrote to compare /dev/spidev writes vs. toggling a GPIO pin to generate a 
500 Hz square wave.  Peak latencies were worse using /dev/spidev but all 
"broke" with missed deadlines and multiple millisecond latencies.  I'd bet 
a lot against ever getting getting 100uS worst case latencies, but would 
love to be wrong.

If you don't have /dev/spidev1.0 in your device-tree overlay, I think all 
you'll need to do is comment out lines 264.265, & 266 that open 
/dev/spidev1.0 and run the program with no arguments which will have it 
attempt to create a 500 Hz square wave on GPIO0_7,  connector P9 pin 42 
 (all the /dev/spidev writes will then never execute). it'll run for five 
minutes and print out some stats at the end.

I haven't tried it in about a year on anything newer than 4.1.x so please 
report back if you do get good results.  The comments at the top explain 
how to build it.  For my use I could have lived with about 1.5 mS worst 
case latency if it wasn't too common.

cyclictest suggested my 1 mS servo loop should be feasible
Running cyclictest on my Pandaboard using a 3.4.x rt kernel from the Robot 
Operting System project:
panda@PandaES:~$ uname -a
Linux PandaES 3.4.0-rt17+ #2 SMP PREEMPT RT Thu Nov 15 13:51:22 CST 2012 
armv7l armv7l armv7l GNU/Linux

panda@PandaES:~/rt-tests$ sudo ./cyclictest -p 90 -t1 -n -l 1000000
# /dev/cpu_dma_latency set to 0us
policy: fifo: loadavg: 0.29 0.28 0.31 1/126 15826          

T: 0 (15823) P:90 I:1000 C:1000000 Min:      9 Act:   11 Avg:   12 Max:     
 26

So add my voice to the chorus saying cyclic test is most likely not telling 
you anything useful for real applications.


On Thursday, March 31, 2016 at 5:59:39 AM UTC-5, Victor WANG wrote:
>
> Hello,
>
> I use the beaglebone black and I've installed Linux debian on it, whitch 
> is a Real Time system  (*Linux beaglebone 4.4.6-bone-rt-r6 #1 PREEMPT RT 
> Thu Mar 17 04:47:32 UTC 2016 armv7l GNU/Linux*), and I want to test the 
> characteristic of RT with cyclictest, 
>
> after using " *cyclictest -p 90 - m -c 0 -i 200 -n -h 100 -q -l 10000", *I 
> got the results:
>
> *# Total: 000010000*
> *# Min Latencies: 00012*
> *# Avg Latencies: 00032*
> *# Max Latencies: 00053*
> *# Histogram Overflows: 00000*
> *# Histogram Overflow at cycle number:*
> *# Thread 0:*
>
> which means the average latencies is 32us, I want to know it is normal or 
> not ? ( I think it is wired, with  the RT patch, it should be better than 
> it. ) 
>
> And if should not be like this, is there something I missed? 
>
> Thanks everyone.
>  
>

-- 
For more options, visit http://beagleboard.org/discuss
--- 
You received this message because you are subscribed to the Google Groups 
"BeagleBoard" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.
// file: swave_panda_dt.c
// produces 500Hz square wave on Pandaboard GPIO pin 32 or on an attached spi D/A chip using /dev/spidev1.0
// you can run it without the D/A chip as long as you have /dev/spidev1.0
// 29NOV2012  [email protected]
// 18AOR2015  modified swave_panda.c to work with device tree kernels GPIO and Raspberry PIv2
// changes in system seem to mean code can only run as root to access the /sys/class/gpio stuff to setup and use the GPIO pins.

 
/* compile using:  
	gcc -o swave_spidev swave_spidev_dt.c -lrt -Wall
   to build without gpio or spidev calls:
   	gcc -D__NO_SPIDEV__ -o swave_spidev swave_spidev_dt.c -lrt 
*/


// uncommnet one to compile on Pandaboard, Beaglebone, or Pi2
// defining __NO_SPIDEV__ runs the code with a delay loop instead of the /dev/spidev write so code can be tested on my destop first.
//#define PANDABOARD
#define BEAGLEBONE
//#define PIv2
//#define __NO_SPIDEV__


/*
	The starting point was the Realtime "Hello World" program from:
	https://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO
	
	With the Pandaboard GPIO stuff from:
	http://www.ozbotz.org/a-simplereal-time-application-on-pandaboard/
	
	I then added my spidev code and some timing tests from my application to illustrate the issue.
*/




#include<time.h>
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<sys/ioctl.h>
#include<linux/spi/spidev.h>
#include<getopt.h>
#include<unistd.h>
#include<linux/types.h>
#include<fcntl.h>
#include<string.h>
#include<sched.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <pthread.h>
#include <signal.h>


#define MAX_SAFE_STACK (8*1024)
void stack_prefault(void) {

        unsigned char dummy[MAX_SAFE_STACK];

        memset(dummy, 0, MAX_SAFE_STACK);
        return;
}


#define NSEC_PER_SEC    1000000000


static inline void tsnorm(struct timespec *ts) {
    while (ts->tv_nsec >= NSEC_PER_SEC) {
        ts->tv_nsec -= NSEC_PER_SEC;
        ts->tv_sec++;
    }
}


// signal handler function, which just tries to provide a graceful exit for short test runs via ctrl-C exit
int QUIT=0;

void handler(int signal){
	QUIT=1;
}


// functions & variables to setup spidev ioctrl for our D/A device
// nice "feature" of spidev is no errors if no hardware attached as long as /dev/spidev1.0 exists
// need chmod 666 on /dev/spidev1.0 to run as non-root user!
// handle differences for Pandaboard, Beaglebone, and Pi
#ifdef PANDABOARD
char *spi_device = "/dev/spidev1.0";		// Pandaboard
// Enable GPIO_32, which comes out on connection 18 of J6 of the Pandaboard. A list of available GPIOs
// can be found in the PandaBoard System Reference Manual.
#define GPIOPIN "32"	
#endif

#ifdef BEAGLEBONE
char *spi_device = "/dev/spidev1.0";		// Beaglebone or BBB with BB-SPI0-01-00A0.dts overlay installed
// Use GPIO0_7,  Connector P9 connection 42
#define GPIOPIN "7"	
#endif

#ifdef PIv2
char *spi_device = "/dev/spidev0.0";		// Raspberry PIv2 device tree overlay
// Use Pin 4, which should be on Connector P1 connection 7
#define GPIOPIN "4"
#endif



int dev1;						// handle to opened spidev
uint32_t speed = 24000000;		// 48000000 max SPI for Panda board, but 24000000 seems to be max shown in spidev dmesg output
// magic numbers for AD5362 dac register data writes
uint8_t dac0=200,dac1=201,dac2=202,dac3=203,dac4=208,dac5=209,dac6=210,dac7=211;
// 8 sets of 3 bytes of data for the dac registers which are big endian data
struct spi_ioc_transfer msg[8];	
uint8_t lead[24];

// function to setup for ioctl of userspace SPIDEV driver
void SPIDEV_transferInitialize(struct spi_ioc_transfer *msg){
	int i;	
	
	// setup sequence of 8 bulk transfers of 3 bytes each
	for(i=0;i<8;i++){
		msg[i].rx_buf = (unsigned long)NULL;
		msg[i].len = 3;
		msg[i].speed_hz = speed;
		msg[i].delay_usecs = 0;
		msg[i].bits_per_word = 8;
		msg[i].cs_change = 1;
		msg[i].pad = 0;
	}

	msg[0].tx_buf = (unsigned long)&lead[0];
	msg[1].tx_buf = (unsigned long)&lead[3];
	msg[2].tx_buf = (unsigned long)&lead[6];
	msg[3].tx_buf = (unsigned long)&lead[9];
	msg[4].tx_buf = (unsigned long)&lead[12];
	msg[5].tx_buf = (unsigned long)&lead[15];
	msg[6].tx_buf = (unsigned long)&lead[18];
	msg[7].tx_buf = (unsigned long)&lead[21];
	
	//initialize channel arrays first byte to be dac register select magic number
	lead[0] = dac0;
	lead[3] = dac1;
	lead[6] = dac2;
	lead[9] = dac3;
	lead[12] = dac4;
	lead[15] = dac5;
	lead[18] = dac6;
	lead[21] = dac7;
}

// function to open SPIDEV
int SPIDEV_open(char *spi_device){
	uint8_t mode = SPI_MODE_1;
	uint8_t lsb = 0;
	int dev1;
	int ret;

	dev1 = open(spi_device,O_RDWR); // might try adding O_SYNC flag, but made no difference on BBB
	if(dev1 < 0) {
		printf("Couldn't open dev1\n");
		return -1;
	}

	ret = ioctl(dev1,SPI_IOC_WR_MODE,&mode);
	if(ret == -1) { 
		printf("mode not set\n"); 
		return -1;
	}
	ret = ioctl(dev1,SPI_IOC_WR_MAX_SPEED_HZ,&speed);
	if(ret == -1) { 
		printf("write speed not set\n"); 
		return -1;
	}
	ret = ioctl(dev1,SPI_IOC_WR_LSB_FIRST,&lsb);
	if(ret == -1) { 
		printf("bit order not set\n"); 
		return -1;
	}
	return dev1;
}


int main( int argc, char** argv )
{
    struct timespec t;
    struct sched_param param;
    int interval=1000000;  // 1000000ns = 1000us = 1 mS interval, ==> 500Hz square wave
	int idiff, maxidiff=0, minidiff=1000000000, passcount=0, mincount=-1, maxcount=-1;
	int spidiff, spimax=0, spimin=1000000000;
	int over=0, over2=0, under=0;
	struct timespec now, prev, enter, leave;
	int do_spidev=0;
	int16_t sample;
	uint8_t *byteptr = NULL;
    int fd;
    char buffer[64];
    unsigned char value = 0;
    int i,j, ret;
    char *ONEstring = "1";
    char *ZEROstring = "0";

	if(argc>=2){
		do_spidev=1;	// flag loop to use SPIDEV instead of GPIO bit
		if(argc==3) spi_device=argv[2];
		printf("Using spidev: %s\n", spi_device);
	}
	byteptr=(uint8_t *)&sample;
	
	//Setup signal handler for graceful exit
	{
		struct sigaction act;
		act.sa_handler=handler;		// try to catch all signals and set QUIT flag for a graceful exit
		sigemptyset(&act.sa_mask);
		act.sa_flags=0;
		sigaction(SIGINT, &act, 0);
		sigaction(SIGHUP, &act, 0);
		sigaction(SIGQUIT, &act, 0);
		sigaction(SIGTERM, &act, 0);
		sigaction(SIGTSTP, &act, 0);
		sigaction(SIGSEGV, &act, 0);
	}
#ifndef __NO_SPIDEV__	
	// enable GPIO pin we will toggle, I believe the sysfs interface works for all ARM kernels >= 3.8
	// and is usable on Pandaboard, Beaglebone, and Raspberry Pi
    {
		// "export" chosen pin so it can be used via sysfs
        if ((fd = open("/sys/class/gpio/export", O_WRONLY)) < 0) {
            printf("Error: /sys/class/gpio/export.\n");
            exit(-1);
        }   
        strcpy( buffer, GPIOPIN );
        write( fd, buffer, strlen(buffer) );
        close(fd);
        if(!do_spidev) printf("Toggling GPIO pin %s.\n",GPIOPIN);
    }

    // Set the direction of the GPIO to "out."
    {
    	snprintf(buffer, sizeof(buffer), "/sys/class/gpio/gpio%s/direction", GPIOPIN);
		fd = open(buffer, O_WRONLY);
		if (-1 == fd) {
			fprintf(stderr, "Failed to open gpio%s direction for writing!\n", GPIOPIN);
			return(-1);
		}
        strcpy( buffer, "out" );
        write( fd, buffer, strlen(buffer) );
        close(fd);
        //printf("Direction set to out.\n");
    }

    // Open the "value" node. We will write to it later.
    {
    	snprintf(buffer, sizeof(buffer), "/sys/class/gpio/gpio%s/value", GPIOPIN);	
        if ((fd = open(buffer, O_WRONLY) ) < 0) {
            printf("Error: Can't open /sys/class/gpio/gpio%s/value.\n", GPIOPIN);
            exit(-2);
        }   
        //printf("Value opened for writing.\n");
    }
    
	// setup SPIDEV transfer
	{
    	dev1 = SPIDEV_open(spi_device);
		if(dev1 < 0) 
			return 1;
		SPIDEV_transferInitialize(msg);    
    }
#endif    
	// if super user -- switch to real-time mode and lock memory pages
	// as is now, must be super user for the GPIO setup to work.
	{
		if (geteuid() == 0) {	   
        	memset(&param, 0, sizeof(param));
			param.sched_priority = sched_get_priority_max(SCHED_FIFO);  
        	if(sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
                fprintf(stderr, "sched_setscheduler failed! Exiting.\n\n");
                exit(-1);
       		}
        	if(mlockall(MCL_CURRENT|MCL_FUTURE) == -1) {
				fprintf(stderr, "mlockall() failed!  Exiting.\n\n");
           		exit(-2);
        	}
        	stack_prefault();
        	fprintf(stderr, " Using realtime priority.\n") ;
		}else {
			fprintf(stderr, " Not running with realtime priority!\n") ;
    	}
	}
	fprintf(stderr, "Square Wave will be generated for 300 seconds (5 minutes) for decent timing statistics.\n");
    clock_gettime(0,&t); // Get current time.
    t.tv_nsec+=interval;
    tsnorm(&t);
	prev.tv_sec=t.tv_sec;
	prev.tv_nsec=t.tv_nsec;
    while (!QUIT && passcount<300000) {		// 300 * 1000 mS = 5 minute run for decent stats
        // wait untill next shot.
        clock_nanosleep(0, TIMER_ABSTIME, &t, NULL);
        clock_gettime(0,&now);
        
        // **** do work ****
#ifndef __NO_SPIDEV__	
        if(!do_spidev){		// this may need changes for the Beaglebone
        	clock_gettime(0,&enter);	// get some timing stats on gpio device time
       		if(value)		// toggle GPIO bit
            	write( fd, ONEstring, 1 );
        	else
           		write( fd, ZEROstring, 1 );
       		value = !value;
        }else{
        	if(value)	// toggle data value between +/- 1/2 full scale to generate square wave 
        		sample=16384;
        	else
        		sample=-16384;
        	value = !value;	
			for(i=0, j=1; i<8; i++, j+=3){
				sample^=0x8000;				// convert to offset binary
				lead[j]=byteptr[1];			// high (MSB) byte into low address of lead array
				lead[j+1]=byteptr[0];		// low (LSB) byte into high addres of lead array
			}
			clock_gettime(0,&enter);		// get some timing stats on spidev time
			// using fwrite(dev1, lead, 24); doesn't fix the latency issue but prevents D/A chip from working because CS stays low for all 192 bits
			ret = ioctl(dev1, SPI_IOC_MESSAGE(8), msg); // 8 denotes how many messages are pointed to by msg array
			if(ret < 1){
				fprintf(stderr, "spidev ioctl error\n");
				break;
			}
        }
#else
		// this is so I can run the code on my desktop before trying it on Panda/Beagle
		//usleep(30);	// pretend it takes 30 uS for SPIDEV ioctl, spidev is way slower that it should be!
		clock_gettime(0,&enter);
		do{				// usleep is "simple" but it seems to lower our priority and can easily be 10X longer than requested
			clock_gettime(0,&leave);
			spidiff=leave.tv_nsec-enter.tv_nsec;
			if(spidiff < 0) spidiff+=1000000000;
		}while(spidiff < 30000);	// ~30 uS spin loop
#endif	
		clock_gettime(0,&leave);		// end time of gpio or spidev timer
        // **** end work ****
        
		// do some stats on how accurate our timing is
		{
			spidiff=leave.tv_nsec-enter.tv_nsec;
			if(spidiff < 0)
				spidiff+=1000000000;
			if(spidiff>spimax) spimax = spidiff;
			if(spidiff<spimin) spimin = spidiff;
			idiff=now.tv_nsec-prev.tv_nsec;
			if(idiff < 0)
				idiff+=1000000000;
			if(idiff>maxidiff){ maxidiff = idiff; maxcount=passcount; }
			if(idiff<minidiff && passcount>0){ minidiff = idiff; mincount=passcount; }
			if(idiff > 1500000)  over++;	// actual sample time more than 50% late (1mS=1000000nS)
			if(idiff >= 2000000)  over2++;	// "dropped" samples, presumabley these are followed by very short intervals.
			if(idiff < 500000 && passcount>0) under++;		// actual sample time more than 50% early
			prev.tv_nsec=now.tv_nsec;
			prev.tv_sec=now.tv_sec;
			passcount++;
		}
        // Calculate next shot.
        t.tv_nsec+=interval;
        tsnorm(&t);
	}
   	// print results
	printf("Interval Max(mS): %3.1f @N=%i    Min: %3.1f @N=%i   Total Samples: %d\n",
			maxidiff/1000000.0, maxcount, minidiff/1000000.0, mincount, passcount);
	printf(" %s time  Max(uS): %-6.1f\tMin: %-6.1f\n", do_spidev ? "spidev" : "gpio", spimax/1000.0,spimin/1000.0);
	printf("Intervals +50%% over N: %d  %3.1f%%  -50%% under N: %d  %3.1f%%\n", over, 100.0*over/passcount, under, 100.0*under/passcount);
	printf( "drops, N intervals >= 2 mS: %d  %3.1f%%\n",over2, 100.0*over2/passcount);
#ifndef __NO_SPIDEV__	
	close(fd);		// value output
	close(dev1);
	// might be wise to unexport the GPIO pin here
    if ((fd = open("/sys/class/gpio/unexport", O_WRONLY)) < 0) {
    	printf("Error: /sys/class/gpio/unexport.\n");
        exit(-4);
        }   
        strcpy( buffer, GPIOPIN );
        write( fd, buffer, strlen(buffer) );
        close(fd);
#endif
   return 0;
}

Reply via email to