Is there a way to make a 18F1320 capture any faster?

Status
Not open for further replies.
Yeah, that is what the documentation I've been able to find has shown, and while the actual models vary the gap between pulses is on the order of a few thousand while the major gap is always on the order of the low tens of thousands. I think I'll go back to trying the filtering method cause the stuff with external interrupts is just not working. I can't get the pins that are listed for external interrupts (B0,1 and 2) to even function as inputs. I'm sure there's a way, but I can't find it. Perhaps if I just connect the first channel, in addition to the ccp pin, to one of the pins that does work as an input, I can check if it's on when the event is triggered and calibrate my channels that way.
 
In the first example code you posted, the one without priorities enabled, you don't set the GIE bit and so you don't get interrupts. You haven't included a config line but I suspect you have DEBUG_ON and this will cause B6 and B7 to become debug pins.

Edit, BTW, the newer spread spectrum systems output all pulses at the same time.

Mike.
 
Last edited:

If you want to strip the receiver down that's fine, but the only outputs from the receiver normally are the individual servo outputs.

But it's absolutrely trivial to read the individual outputs - even the Mk1 BASIC STAMPS can do it (which ran incredibly slowly), and it was a very common application for them.

The OP seems to be over complicting things by trying to use interrupts to do it, rather than simple polling.
 
even the Mk1 BASIC STAMPS can do it (which ran incredibly slowly), and it was a very common application for them.

Hi, Nigel

The problem is ... Basic stamps and generally basic compilers have Pulsin and RCTime commands that DO NOT USE any timer ...

Basically the sequence then is :

Pulsin Ch1
RCTime Ch2
RCTime ch3
RCTime ch 4
....

On this basis, I built a 4 Channel fail-safe device ... controlling each frame.

BUT "C" Compilers do not offer the equivalent " full software " commands ... that might be "customer included" as pure assembler functions ( nice piece of cake, eh ??? ...).

as an info ... the time between Ch1 falling edge and Ch2 rising edge is about the 10 Nanoseconds ... of course for CMOS decoder chips. if decoder is a µP ... consider 200-500 Nano secs, which is far too short for inputs Polling.
For last systems ... channel output can be 2 per 2 , 3 per 3 or all together ...

so, the only way is to catch the serial frame @ the output of the receiver part ...

now, if it is only for displaying purposes ... no use to read all the channels in a single frame ...
one channel or two per frame can be measured ... won't be a problem.

Alain
 
Last edited:
Hi, Nigel

The problem is ... Basic stamps and generally basic compilers have Pulsin and RCTime commands that DO NOT USE any timer ...

Yes I know, why would you want to use a timer?.


It would be trivial to write a 'pulsein' commend in C, just as it is in assembler.

as an info ... the time between Ch1 falling edge and Ch2 rising edge is about the 10 Nanoseconds ... of course for CMOS decoder chips. if decoder is a µP ... consider 200-500 Nano secs, which is far too short for inputs Polling.

BASIC STAMP's manage it, 12F sries manage it, 12 series amange it as well - there's loads of time for polling.

For last systems ... channel output can be 2 per 2 , 3 per 3 or all together ...

so, the only way is to catch the serial frame @ the output of the receiver part ...

Alain

Funny how there's loads of amateur and commercial PIC based products which happily read the individual servo outputs.

Check some old examples here:
 
Yes I know, why would you want to use a timer?.

I ... do no want !!! but "C" coders ... yes !

It would be trivial to write a 'pulsein' commend in C, just as it is in assembler.

Probably Trivial ... never saw such piece of code, but lots of threads asking for it ...

BASIC STAMP's manage it, 12F sries manage it, 12 series amange it as well - there's loads of time for polling.

NO, Nigel ... with Basic , or polling you read the first channel of the first frame, then the 2nd channel of the SECOND frame, etc, etc ... PULSIN needs some µs ( I measured 4 µs @ 4 Mhz ) to " re-arm " its leading edge detector ... polling also need time between edges ...

Funny how there's loads of amateur and commercial PIC based products which happily read the individual servo outputs.

reading individual channels is very easy ... but reading same frame channels following each others is somewhat more difficult ...

Alain
 
Probably Trivial ... never saw such piece of code, but lots of threads asking for it ...

It's a pair of simple software loops, my IR SIRC's tutorial does the same thing to time the IR pulses.

There is (or was) a series of pages on the Dontronics website (from a book) that gave assembler replacements for all the STAMP commands.

Here you go:




There's a big enough gap in between the servo pulses to capture each one in turn - but you obviously must capture them in the correct sequence.
 
It may be because of my inexperience, but I wasn't able to make polling fast enough to accurately capture the PWM pulses. I would get a number between 180 and 250 at the high and and between 120 and 190 at the low end. Perhaps now that I have a better understanding of timing I could do it better.

By the way, I also have an amateur program to capture one servo signal very effectivly:
Code:
//Basic configureation of chip periferials

#include <timers.h>
 #include <p18f2550.h>
 #pragma FOSC = INTI0, WDT = OFF, LVP = OFF

//setup pins for PWM input
#define ReceiverPin PORTBbits.RB3
#define ReceiverTris TRISBbits.TRISB3


//PWM capture variables
unsigned int PWM1RiseTime = 0; //timer value at rising edge capture
unsigned int PWM1Width = 0; //calculated width
char PWM1Edge = 1; //edge currently being monitored 1 = rising, 0 = falling 

//set up interrupt
void low_ISR(void);//prototype
#pragma code low_vector = 0x08
void low_interrupt (void){
_asm goto low_ISR _endasm
}
#pragma code
#pragma interrupt low_ISR

//debug stuff
char test1 = 0;
char test2 = 0;

void main(void)
{	
	unsigned int MotorCount = 1;
	unsigned char MotorOn;	

	OSCCON = 0x72; //8MHz clock
	while(!OSCCONbits.IOFS); //Wait for OSC to become stable 

	//configure timer1
	OpenTimer1(0b10010101);
	PIE1bits.TMR1IE = 1; //periferial interupt register, 1 = timer 1 interupt enabled
	PIR1bits.TMR1IF = 0; //clears the timer 1 interupt flag

	//configure CCP1
	CCP1CON = 0b0000101; //configure CCP1 for capture, rising edge
	INTCONbits.PEIE=1;		//enable peripheral interrupts
    PIE1bits.CCP1IE=1;		//enabled CCP1 interrupt
    INTCONbits.GIE=1;		//enable branching to interrupt
	
	ReceiverTris = 1; //set RB3 for input so the capture can work.
	
	//configure ports
	ADCON1 = 0xff; //all digital
	INTCON2bits.RBPU = 0; //port b weak pullups on
	
	//configure control bits for output
	TRISBbits.TRISB5 = 0;
	TRISBbits.TRISB4 = 0;
	TRISAbits.TRISA4 = 0;
	//enable bit
	LATAbits.LATA4 = 1; //Enable bit for cl298
	

	LATBbits.LATB5 = 1;//testing
	LATBbits.LATB4 = 0; 

	while(1)
	{
		

	}
}


void low_ISR(void)
{		

	test1 = 1;
	//ccp interrupt
	if(PIR1bits.CCP1IF == 1)
	{	
		PIR1bits.CCP1IF = 0; //clear the flag
		if(PWM1Edge == 1)//if detecting rising
		{	
			
			PWM1RiseTime = CCPR1;//save the low timer value for the rise time
			CCP1CON = 0b0000100;//switch to detect falling edge
			PWM1Edge = 0;//switch to indicate falling edge is next
		}
		else //detecting falling
		{

			PWM1Width = CCPR1 - PWM1RiseTime;
			CCP1CON = 0b0000101;//switch to detect rising edge
			PWM1Edge = 1;//switch to indicate rising edge is next
		}

		//This stuff is just for testing
		if(PWM1Width > 1800)
		{
			LATBbits.LATB4 = 0;
			LATBbits.LATB5 = 1;
		}
		
		if(PWM1Width < 1400)
		{
			LATBbits.LATB4 = 1;
			LATBbits.LATB5 = 0;
		}

		if(PWM1Width > 1400 && PWM1Width < 1800)
		{
			LATBbits.LATB4 = 1; //should be 0 if not testing
			LATBbits.LATB5 = 0;
		}
		
	}
}

it only varies by 0.5% with the control in a given position. I have projects that I'm doing to get things done, and if this were one of them I would just stick two of these chips on a board and be done with it. But this one is for practice. In my line of work the stuff I learn from it could definitely come in handy though (I'm a grad student that contracts to make custom lab equipment).

So now my plan is to try to make a polling method one and see if I can get the timing to work better, and to try the ccp module based code above, with the diode filtering method augmented by using a different pin to tell the pulses apart. Later this week when I can get at an oscilloscope I can confirm if the pulses overlap or not.

Thanks again for all the help!
 
The 18F1330 can often be used in place on the 18F1320 and runs 4 times faster using the internal clock and PLL. It has support for advanced PWM (motor control) and you loose one of the timers. If you can live with that it is a great choice. You also get 3 breakpoints instead of 1.
 
I gathered some data from my receiver using a DAQ device, it may not be the best tool but it's what I have. Its a max 14400 sample/second device, so when monitoring 3 channels it can take 4800 samples per second, 7200 if I only watch 2 channels. It appears that some devices, including the main one I have been using for this, start one pulse the instant the previous pulse ends. That's probably why with the one pin solution, even with filtering I was getting a reading of above 8000 on all channels no matter what I did, that's about 3X the pulse length of just one. So that makes sense, it wasn't seeing a dip so it was just reading one long pulse. Other devices start their pulses on even intervals, such as one every 10ms, then a 40ms gap from the start of the last one to the start of the first.

So now the question is, what is the fastest I can possibly poll 2 input pins (3 would be even better) with an 18F1320 and how do I do it? I can come up with some ways, but I'm sure the more experienced people here can give me tips to make it far more efficient than I know how to do. It's also going to have to be able to multitask to generate 2 (or 3) control signals. Since the pulses are 1.5 to 2.5 ms long, to even get 20 degrees of accuracy I would need to poll the pins 10K times a second, I know the 1320 is a 8Mhz chip, but I'm not well versed in timer settings or command execution times. I'm only up to the point in timers where I can make them trigger an interrupt, and I can generally make them happen faster or slower. So I'm not sure if polling 3 pins at 10Khz each and generating 3 reversible pwm motor control signals somewhere in the low Khz range is reasonable to attempt or not.

By the way, is it normal for some of these devices to send the pulses end to end like that? I haven't heard anyone mention it, and I realize that it's possible that at 7200 hz sample rate I just can't see the gap, but their starts do seem to shift as if relative to the end of the last pulse rather than the beginning, as in futaba and other more common devices.

Here's my readout on the device:
**broken link removed**
 
Last edited:
Don't know if you have a PICkit 2 programmer or not? The PICkit 2 logic tool will provide much a better resolution. With the 100khz sample rate, you get a 4 ms window which should be just right.

My Vex transmitter had a rather large static gap between consecutive signals, on the order of 0.4-0.5 ms. Definitely made life easy.

Your If PortB.0 ==1 loop code from a previous post is the max resolution that you will get. For a Byte sized counter variable, the assembler works out to four instructions, (1)incf, (1)btfsc, (2)goto. The timing is then 4 x 0.5µs (8Mhz/4) = 2µs, or 500,000 samples a sec.

The problem with a 2µs loop is that the counter variable will overflow, before you even get to 1ms; that's why 10us loops work good with servo code. Adding 16 nop's, or a 8us delay, to the If PortB.0==1 loop should give you a 10µs loop (8Mhz clock). Even with a "no gap" pulse train, and "inlining" the bit test loops, the error will be on the order of 1-2%.

Best to concentrate on just forward, neutral, reverse for the motors at first. Unless the motors generate massive torque, or have high ratio gear boxes, low PWM values will just not overcome the static inertia to get things rolling.
 
I would use the interrupt on change feature of port B and timer 1 incrementing at 1uS (T1CON=0x91).

My guess at an ISR to do this would be,
Code:
//must be global
unsigned char Previous;
unsigned int T1High,T1Period;

void low_ISR(void)
{			
unsigned char Changed,Port,NewHigh,NewLow;
unsigned int Time;
    Time=TMR1L;				//read low byte and latch high
    Time+=TMR1H<<8;			//read latched high byte
    Port=PORTB;				//read the port
    if(INTCONbits.RBIF)			//Must be this but check anyway
    {
        Changed=Previous^Port;		//see which bits have changed
        NewHigh=Changed&Previous;		//and which are new highs
        NewLow=Changed&~Previous;		//also new lows

        if(NewHigh&0x10){			//did B4 go high
            T1High=Time;			//yes, so store time
        }

        if(NewLow&0x10){			//did B4 go low
            T1Period=Time-T1High;		//yes, so calculate period
        }

         //repeat for B5,B6 and B7 as required

        Previous=Port;				//store current state of PortB
        INTCONbits.RBIF = 0;			//reset interrupt flag
    }
}

Reading the timer and the port at the beginning of the ISR is important for accuracy.

Note, this is untested but I can't see why it wouldn't work.

Mike.
 
Last edited:
Thanks Mike, I didn't think to just capture port B all at once, I don't know how that didn't occur to me. You did some other clever stuff too, I'll try and implement that code soon and see.

By the way, I'm at my university today and I got a chance to use an oscilloscope on the receiver, It confirmed that on this particular one (Traxxas model 3710 3 channel system) the pulses are simultaneous. That seems strange to me since the way I always read that these work is with a serial signal of pulses which are chopped up by a latch device to the different servo ports, since it's only using one frequency I cant see how it would do anything else. So it seems like this device must interpret the signal, then generate its own on its own clock. That would be easily done with today's micro controllers, but why would they bother?
 
I suspect that the signal sent over the air is a stream of pulses that are shorter than the required servo pulses with a short gap to make up the total time. At the receiver end only the leading edges of the pulses are used to clock a 4017 type device to separate the pulses.

Mike.
 
I just noticed that I had NewHighs and NewLows the wrong way around.

Code:
        NewHigh=Changed&Previous;		//and which are new highs
        NewLow=Changed&~Previous;		//also new lows
should be,
Code:
        NewHigh=Changed&~Previous;		//and which are new highs
        NewLow=Changed&Previous;		//also new lows
It probably makes more sense logically to do,
Code:
        NewHigh=Changed&Port;		//and which are new highs
        NewLow=Changed&Previous;		//also new lows

Mike.
 
Last edited:
Thanks! I haven't managed to implement that solution yet. But I found another one that works atleast for this kind of receiver. A friend of mine in electrical engineering showed me how to get a more real-time readout of the output, and it became clear that the pulses were actually shifting their start position so that they started right when the last one ended. The visualization gave me an idea that would only work for odd numbers of channels, which is fine for my 3 channel reciever; use one pin to capture the odd numbered ports, and count the width of the gaps as well. If a pulse comes after the big gap then it is pulse width 1, the first gap is pulse width 2, the second pulse is pulse width 3 and so on. Right now its working very well as a one pin input solution.

Code:
//Basic configureation of chip periferials
 #pragma config OSC = INTIO2, WDT = OFF, LVP = OFF
 #include <p18f1320.h>
 #include <delays.h>
 #include<timers.h>

//setup pins for PWM input
#define ReceiverPin PORTBbits.RB3
#define ReceiverTris TRISBbits.TRISB3

//PWM capture variables
unsigned int PWM1RiseTime = 0; //timer value at rising edge capture
unsigned int PWM1FallTime = 0; //timer value at falling edge capture
unsigned int PWM1Width = 0; //calculated width

unsigned int channel1_width = 0;
unsigned int channel2_width = 0;
unsigned int channel3_width = 0;
unsigned int PWMGap = 0; //calculated gap between pulses

char PWM1Edge = 1; //edge currently being monitored 1 = rising, 0 = falling 
unsigned char Channel_Being_Captured = 1; //this will record which channel is being watched


//set up interrupt
void low_ISR(void);//prototype
#pragma code low_vector = 0x08 //0X08 IS LOW 0X18 IS HIGH
void low_interrupt (void){
_asm goto low_ISR _endasm
}
#pragma code
#pragma interrupt low_ISR

void main(void)
{	


	OSCCON = 0x72; //8MHz clock
	while(!OSCCONbits.IOFS); //Wait for OSC to become stable 

	//configure timer1
	//OpenTimer1(0b10010101);
	//PIE1bits.TMR1IE = 1; //periferial interupt register, 1 = timer 1 interupt enabled
	PIR1bits.TMR1IF = 0; //clears the timer 1 interupt flag
	T1CONbits.TMR1ON = 1; //turn on timer
	T1CONbits.T1CKPS1 = 0; //set prescaler
	T1CONbits.T1CKPS0 = 0; //set prescaler


	//setup timer2
	OpenTimer2(TIMER_INT_ON);
	PIR1bits.TMR2IF = 0; //clears the timer 2 interupt flag
	PR2 = 20;
	T2CON = 0b00000100; //(-)always 0 (----) postscale (-)on/off (--) prescale

	//configure CCP1
	CCP1CON = 0b0000101; //configure CCP1 for capture, rising edge
	INTCONbits.PEIE=1;		//enable peripheral interrupts
    PIE1bits.CCP1IE=1;		//enabled CCP1 interrupt
    INTCONbits.GIE=1;		//enable branching to interrupt
	ReceiverTris = 1; //set RB3 for input so the capture can work.
	TRISBbits.TRISB2 = 1; //set rb2 for in so it can be used to differentiate channels
	
	//configure ports
	ADCON1 = 0xff; //all digital
	INTCON2bits.RBPU = 0; //port b weak pullups on
	
	//configure control bits for output
	TRISAbits.TRISA0 = 0;
	TRISAbits.TRISA6 = 0;
	//controll bits for second motor
	TRISAbits.TRISA1 = 0;
	TRISAbits.TRISA4 = 0;	

	while(1)
	{
	}
}



void low_ISR(void)
{		
	

	//Timer 2 flag
	if(PIR1bits.TMR2IF == 1)
	{
		PIR1bits.TMR2IF = 0; //clears the timer 1 interupt flag
	}


	//ccp interrupt
	if(PIR1bits.CCP1IF == 1)
	{	
		PIR1bits.CCP1IF = 0; //clear the flag
		if(PWM1Edge == 1)//if detecting rising
		{
			CCP1CON = 0b0000100;//switch to detect falling edge
			PWM1Edge = 0;//switch to indicate falling edge is next
			PWMGap = CCPR1 - PWM1FallTime; //calculate gap between pulse starts
			PWM1RiseTime = CCPR1;//save the low timer value for the rise time
			
			
			if(PWMGap < 10000){channel2_width = PWMGap;}
			
			//if (PWMGap>25000){Channel_Being_Captured = 1;}



		}
		else //if detecting falling
		{
			CCP1CON = 0b0000101;//switch to detect rising edge
			PWM1Edge = 1;//switch to indicate rising edge is next
			PWM1Width = CCPR1 - PWM1RiseTime; //(pwm rise time is the time that the pwm rise occured)
			PWM1FallTime = CCPR1;//save the low timer value for the fall time

			if(PWMGap > 10000){channel1_width = PWM1Width;}
			if(PWMGap < 10000){channel3_width = PWM1Width;}
		}

		
	}
}

But this isn't the end of my project because this obviously wont work with the other kind of receiver, the ones that start their pulses one regular intervals and then have a set gap between the start of the last and the start of the first again. The only way to make one work with either type, and other types that might exist that use standard servo timing is to use separate ports.
Still for now the specific solution I've found lets me get on with other parts of my project and come back to servo signal reading later, which I am presently pretty tired of.
 
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…