Help to capture RC reciever servo channels

Hi ,

i am using an ATMEGA128 to control 4 servos , but i am stuck as to how to read /capture 4 channels of the Futaba Reciever, using 4 input pins one for each reciever channel , so i can act on the position of and servo channel input ,

driving servo’s is not the problem , i just can’t get my head around capturing the reciever channels independantly

anyone have any code would be a big help

in winavr would be great !

Cncbasher

Hi,

Sounds like you’re trying to capture or “read” a PWM signal, correct? I’m assuming thats what the remote would output, a PWM signal compatible with a hobby servo. That being a pulse that occurs every 20ms, that varies in length from roughly 1ms to 2ms, with 1.5ms as the “center” value. I dont know anything about RC remotes, but I’m assuming thats how they work, since most of the hobby servos work that way.

That being the case, there are few ways that immediately come to mind.

Method 1: Create a 16 bit timer with sufficient accuracy, poll the pin, when it goes high, save the timer value, when it goes low, save the next timer value. The difference between the two will be the length of time of the pulse. This of course becomes problematic with multiple pins, or if you want to be able to do something else while you’re waiting on the pin transition.

Method 2: A more advanced way would be to use the External Interrupt pins to trigger the clock read. You create an interrupt routine that responds to pin transitions on one of the INTx pins. In your interrupt routine you ascertain which transition you’re on (rising or falling), and capture the clock, possibly doing the math needed to save the pulse width.

Method 3: Yet another way might be to use the Input Capture capabilities that some of the timers have. Problem with this is you may not have enough input capture units to read 4 channels, I think you get one per 16bit counter, meaning you have a total of two.

I’ve used the second technique (interrupts) to read a PWM off a dual axis accelerometer on a Atmega8, and it worked pretty well. An Atmega 128 has more than enough external interrupt pins to do that, I think, and you would only need one timer. you probably want to use a 16 bit timer/counter for accuracy, the 8 bit is probably not accurate enough… You may even be able to dual-purpose one of the timers you are using to generate PWM for the servos (i’m assuming youre using the 16bit timers to generate pwm for that).

If you come up with any other ways, let me know…

I have been able to read 7 channels in the background of an 8mhz AT90S8515. I used the 8 bit timer to create a periodic interrupt and let the 16 bit timer run free. During each periodic interrupt (32khz I believe), read the port containing the channel data, compare with the prior value and return if nothing changed (keeping everything in registers for speed). In the event that something changed, read the 16-bit timer to get a timestamp and determine the bit transition direction. On low to high you store the timer value. On high to low you subtrack the previously stored value from the current value (note-- might have those backwards). You then rescale the final value. Add calibration to find the min/max for each channel and you are set.

As I recall, I got pretty decent 7-bit resolution with this approach.

On a newer chip like the 128 that has lots of interrupts, I would probably use method #2 that James mentions.

Vraz,

Interesting technique, I hadn’t thought of that…

If I understand it correctly, the accuracy is basically set by the periodic interrupt frequency, so at 32khz you would be able to get readings down to about +/-30 microseconds.

For reading Servo-type PWM, you might want it to be a bit more accurate, as that would give you about 32 steps from 1 to 2 ms. Increase the periodic frequency a bit if more accuracy is required. You just have to be careful that the interrupt routine can execute inside of the periodic frequency in the worst case. The downside is the continuous polling steals a lot of cpu away from other tasks, the higher the accuracy, the more it steals.

I can definately see the upside to your technique if you need to grab alot of channels, particularly if its more channels than you have external interrupts. I’ll have to remember that one!

I agree that the method #2 that I listed is still /probably/ a better choice for this situation, assuming “cnc” has the interrupt pins to spare, it will require less overall cpu utilization (no polling).

I find the problem with regular PWM control of servos from a uC is that timers is designed to provide 8 (or 10, or whatever)-bit accuracy over the entire duty cycle (in this case 16-20ms), instead of over the 1ms portion of the duty cycle that you actually care about.

If you’re trying to control more than two or three servos, your uC probably doesn’t have enough external interrupt pins (I think mega128 only has two? three?). For lots of servos, I would probably use Vraz’s solution…

If I understand it correctly, the accuracy is basically set by the periodic interrupt frequency, so at 32khz you would be able to get readings down to about +/-30 microseconds.

Good point. This is not consistent with the 7-bit resolution I quoted. Reviewing my old code in more detail, I ran at 256khz (an interrupt every 32 machine cycles). In the "nothing changed" case, the handler uses 16 cycles (50% of the CPU). I used the 16-bit timer in order to avoid burning even more cycles. While 50% sounds like a lot, since its evenly distributed, its not noticable in most applications.

I just glanced at the mega128 pinout and it is tight on interrupts (I thought it had an entire port of ints, but was thinking of the mega48). Since the mega128 runs at 16mhz, that drops the CPU cost down to 25%. I am sure you could drop the interrupt rate down to 128khz without too much impact. I would not suggest going much lower.

I used the code to do tank style mixing for a bot and the 7-bit resolution gave nice results. For the PWM output part, I used the same 16-bit timer to figure out the PWM width and took advantage of the A/B compare interrupts since I was only controlling two servos. With that said, you could easily control more outputs using a single compare register by setting all outputs to high at cycle start, finding the shortest cycle and using that for the first compare value. When it hits, you drop those bits (may be more than one), find the next lowest and repeat.

Vraz:
I used the code to do tank style mixing

Off topic, but if you have an example of an algorithm I can use for this, or some source code you could share, I’d be extremely interested. I’m trying to figure out tank-style signal mixing, but can’t seem to get a handle on it…

Here is the tank control algorithm I used:

Start with 2 inputs:

left/right = signed value, zero=straight

fwd/reverse = unsigned value, zero=full reverse, mid=stop, max=full fwd

left-tread = unsigned(fwd/reverse) + signed(left/right)

right-tread = unsigned(fwd/reverse) - signed(left/right)

clamp the left-tread value

clamp the right-tread value

The clamp algorithm does not let the value underflow or overflow. In addition, it also takes the “center range” and forces it to a single “stop” value (else it will jiggle due to noise). In my case, I use 8-bit data (7-bit samples, recalibrated and rescaled to 8-bits).

Its not perfect and needs some tuning, but this is the basic algorithm.

Roach,

Yeah, 8-bit timers are out for driving hobby servo PWM directly… 16-bit timers work really well with the right prescaler. Several of the lower end AVRs have one 16-bit timer with usually 2 Output Compares, so you can drive two servos. The Atmega128 has two 16-bit timers with 3 output compares each. Of course Atmel sells a huge variety of AVRs, I need not mention the capablities of all of them.

Vraz,

The atmega 128 does have 8 external interrupts, they are split between ports D and E. I’m not sure if all 8 have exactly the same capabilities or not.

I’ve tried a variety of methods for /generating/ servo PWM, and hardware (Output Compare on Timer) is definately best if you can use it. I’ve run PWM via software as you describe, but it can be a huge PITA because you can get visible jitter if the timing varies by as little as a microsecond. Worse, if you’re starting the pulses for several servos at the same time, getting all the pulses to stop at the correct time can be problematic in situations where, say, the servos are set to values close to each other. Imagine one servo being set 1us different than the next one. It can probably be done (some hand rolled assembly interrupt routines with a fast enough crystal, maybe), but what a pain… And it limits what you can do elsewhere (I dont recommend trying to mix interrupt driven software I/O of some other type)

In my case I was trying to drive 3 servos with software using an Atmega32 (that was doing several other things too), at some point I got tired of fighting with it and went with a 128 so I could do up to 6 with hardware. Much simpler, no more timing issues, no more jitter.

Wow, this has really strayed off topic… Is the original poster still around? Did he get anything out of all this? :slight_smile:

Let’s stray a little more. One of the honorable mentions in Circuit Cellars AVR2004 design context was this awesome 32-servo controller that uses a dedicated FPGA for generating PWM signals, and some low-end avr, running at (I think) 8MHz to command the FPGS.

Check it out [here](http://www.circuitcellar.com/AVR2004/HA3722.html)

Roach,

Yer killin me! :slight_smile: Seriously though. Yeah, I had seen that previously. I’ve even seen AVRs and PICs used in dedicated servo boards that drive many more servos that would be possible strictly with hardware, it can definately be done. The problem is, if you’re driving a bunch of servos with software, you’re not going to be able to do too much more with that one chip without running into trouble.

I think the original poster was trying to actually /read/ PWM on the same chip that he was also outputing PWM. (Why? Not sure). I assumed he was probably using this for some kind of robotic control maybe? Anyway, the presumption was that he needed to be able to do other stuff with the CPU along with reading and outputing PWM. No matter, I’m sure we’ve scared him off by now :slight_smile:

The atmega 128 does have 8 external interrupts, they are split between ports D and E. I’m not sure if all 8 have exactly the same capabilities or not.

Doh! I missed the ones on Port E. So 8 interrupts is not too bad. Clearly the efficient way to do the sampling.

I’ve tried a variety of methods for /generating/ servo PWM, and hardware (Output Compare on Timer) is definately best if you can use it. I’ve run PWM via software as you describe, but it can be a huge PITA because you can get visible jitter if the timing varies by as little as a microsecond.

Interesting. I have only used hardware as well since my application only needed two outputs and it was easier.

With that said, I am pretty confident you can do it in software using a single 16-bit timer and single compare interrupt with about as many pins as you want and no significant jitter. The hardware timer / compare provides the precise timing required. Remember, this is PPM, not true PWM so there is not a regular cycle as such. Because the updates are in parallel, you will get the exact same timing if multiple channels have the same value (if using different output ports there may be a few processor cycles of jitter, but the servo won’t notice). That AVR project is cool, but I think he could have skipped the FPGA.

I think the original poster was trying to actually /read/ PWM on the same chip that he was also outputing PWM. (Why? Not sure)

Mine was so that I could effectively post-process the R/C data on the controller before sending it to the servos. Aside from tank mixing, another channel allowed for inversion control (was going to add an actual sensor to the AVR but never got that far). "Assisted R/C" is a pretty cool concept for bot control.

thanks all for the comments and ideas , now for improving on the theme

/*****************************************************************************************
Title:    MOSFET Radio Control  DC motor controller
Author:   D Armstrong dud.one@ntlworld.com
File:     $Id: motor.c, v 2.1 22/4/2006 $
Software: AVR-GCC 3.4.5  ( Winavr 20060125 )
Hardware: AT90S2313 at 10.0MHz or atmega128

Copyright (c) use for whatever purpose you wish , change modify as you want, but dont blame me if it all goes pearshaped
all i ask is , if you modify or can improve please email a copy back and i will incorporate changes 
for future users . it would be nice to remove the scale factor and have calibration in code  saved in eeprom etc  

DESCRIPTION:
   The board is designed to control a DC motor for a remote control application, notably RC Model Aircraft. It takes input
   from a standard RC receiver and produces a PWM output to drive a motor via a MOSFET circuit. It also monitors
   battery voltage and disables to motor when it becomes too low.

   The main task is to poll the incoming signal on PB4 to see when it changes. The period is about 22ms. For the
   JR Propo R700 the pulse width is between 1.08ms and 2.04ms. The trim can vary this from 1.02ms to 2.15ms.
   Set up timer 0 to clk/256 = 25.6 microseconds, and look for a pulse width count more than 43 (1.08ms) and less
   than 78 (2.04ms). This leaves a band at the lower and upper ends for safety. We do this by polling the signal
   until it turns on high, then start timer 0. Continue polling until it is low, then read the timer.

   Once we have the pulse width count, we will generate a PWM signal using the timer 1 interrupt. Clock timer 1
   at ck/8 giving a 0.8 microsecond resolution. If the incoming pulse width is between 43 and 78, scale up by 8.
   An empirical maximum count of 230 gives a PWM period of about 210us (about 5kHz). Start the timer for
   the first part of the PWM cycle, check for comparator match, and toggle the PWM output. Set the timer to count
   for the remainder of the cycle, then toggle the PWM output, and so on.

   A section of code is activated once every "timer_count" cycles to provide a place for execution of infrequent
   events. This includes battery checks and lost signal checks. The motor speed changes occur here also, even though
   the computation of speed change is done every cycle and smoothed.

   A valid signal is one that falls within a range of pulse widths from just below the lowest to just above the
   highest (using about 130us safety margin). On initialisation this valid range is much reduced to just above the
   minimum pulse width.

   When a sequence of valid signals exceeds a count of 16 in a row, then the signal is considered good and a change
   of motor speed is permitted. The absence of this good signal indication is also used to determine if a signal
   has been lost. As a lost signal can result in a failure of pulses to arrive, a variable is used to track this
   condition. Make sure that at least five pulse periods are present within the "infrequent event" period,
   otherwise a false "lost signal" can occur, disabling the motor prematurely. A timer_count of 10000 seems to be
   adequate for a 10MHz clock. The R700 produces random pulses out (response to noise) while the JETI Rex 5 simply
   stops in a high or low state when signal disappears.

   The motor is not enabled until a valid signal that gives a motor off condition is first detected. That is, once
   the motor has been disabled by an event (usually battery low but also on startup), the operator must reduce the
   control to zero in order to enable it. The motor is turned off if the signal disappears for a given period of
   time (nominally 1 second).

   A battery low signal, detected by the analogue comparator, will turn off and disable the motor. If the
   battery recovers, the motor can be re-enabled automatically or by the normal manual process. 

	
*/

/* code is compiled for AT90S2313 and will compile for Atmega128
   by adding the following defines it should work on others
   without too many changes */

 
#define OCR1L    _SFR_IO8(0x2A)
#define OCR1H    _SFR_IO8(0x2B)



#include <avr/io.h>
#include <avr/wdt.h>

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

const unsigned short in_pw_min=43;		        /* Minimum input pulse width */
const unsigned short in_pw_max=78;				/* Maximum input pulse width */
const unsigned short PWM_period=232;			/* Count for PWM period timer (multiple of 8 best) */
const unsigned short scale=9;					/* Scale factor to convert input to PWM */
const unsigned short signal_lost_count=8;		/* Countdown to determine if a signal is lost */
const unsigned short pulse_threshold = 4;		/* Count to determine if a valid pulse occurred */
const unsigned short battery_threshold = 4;		/* Count to determine if a battery low has occurred */
const unsigned short timer_count = 10000;		/* Count for infrequent event timer loop */
const unsigned short PWM_step = 10;				/* Step for motor speed on ramp up/down */

/*		GLOBAL STATE VARIABLES										*/

unsigned short pulse_present;				/* A valid pulse is in progress */
unsigned short pulse_started;				/* A pulse start has been detected */
unsigned short pulse_ended;					/* A pulse end has been detected */
unsigned short motor_enable;				/* Use to keep motor off until conditions right */
unsigned short battery_down;				/* A low battery voltage has been detected */
unsigned short battery_low;					/* A low battery voltage has been determined */
unsigned short pulse_width;					/* A measure of the input pulse width */
unsigned short PWM_width;					/* PWM width setting for intermediate speed */
unsigned short PWM_final_width;				/* PWM width setting for final speed goal */
unsigned short PWM_on;						/* The PWM cycle is in the "on" period */
unsigned short PWM_limit;					/* Speed limit on motor */
unsigned short PWM_count;					/* Value read from PWM timer */
unsigned short PWM_active;					/* Allow PWM processing to occur when needed */
unsigned short signal_check;				/* Timer to check if a signal is present */
unsigned short valid_signal;				/* Indicates the input is in a valid range */
unsigned short good_signal;					/* Indicates a strong signal is present */
unsigned short timer;				        /* Used to create a time delay loop for infrequent events */
unsigned short pulse_detected;				/* We need this to find out if pulses are actually occurring */



int main(void)
{

/*******************************************************************************************************/
/* Initialise hardware timers and ports */

#define INIT_HARDWARE ({        						\
	TCCR0 = 0;					        /* Stop timer 0 */	\
	TCCR1B = 0;					        /* Stop timer 1 */	\
	TCCR1A = 0;					        /* Disable hardware actions on timer 1 */\
	OCR1H = 0;					        /* Clear the Output Compare Register */	\
	OCR1L = 0;							\
	DDRB = 0xEC;					    /* Set unused B ports and B3 to output */\
	ACSR = 0;					        /* Enable comparator */ \
	(PORTB) |= (1 << (PB3));			/* Turn off motor */	\
})

/*******************************************************************************************************/
/* Initialise the global variables used in tracking states */

#define INIT_GLOBALS ({									\
	motor_enable = 0;					/* Keep it disabled for now */		\
	signal_check = 0;					/* Use to check if a signal is present */\
	pulse_started = 0;					/* Use to test if a pulse starting */	\
	pulse_ended = 0;					/* Use to test if a pulse ending */	\
	pulse_detected = 0;					/* No pulses detected */		\
	battery_down = 0;					/* Used to test if a battery low condition occurred */\
	PWM_width = 0;						/* Intermediate fraction of PWM cycle to activate */\
	PWM_final_width = 0;										\
	PWM_on = 0;							/* Start PWM in the "off" cycle */	\
	PWM_limit = PWM_period; 									\
	valid_signal = 0;					/* Use to filter out bad signals */	\
	good_signal = 0;										\
	timer = 0;							/* Start the infrequent event timer*/	\
})

/********************************************************************************************************/
/* Configure and reset timer 0 */

#define INIT_TIMER_0 ({										\
	TCCR0 = 0x04;					    /* Start timer 0 clk/256 (up to 8ms count) */\
	TCNT0 = 0;					        /* Clear timer 0 to start counting */\
})

/********************************************************************************************************/
/* Reset timer 1 (16 bit timer) */

#define RESET_TIMER_1 ({					\
	TCNT1H = 0;								\
        TCNT1L = 0;							\
})

/**********************************************************************************************************/
/* This processes several state variables to determine if a pulse is starting, by waiting for pulse_threshold
   cycles. It then determines if the pulse is finishing by waiting a similar amount of time. This type of
   filtering may be overkill, hardware filtering in the receiver could make it redundant. */

#define TEST_PULSE ({											\
	if (bit_is_set(PINB,PB4))								/* Check to see if a pulse is starting */\
	{ if (pulse_present && pulse_ended) pulse_ended = 0;	/* Abortive pulse end detected, so clear count */\
	  if (! pulse_present)				        			/* Looks like the start of a new pulse */\
	    pulse_present = (pulse_started++ > pulse_threshold);/* Bump start verification count and test */\
	}												\
	else												\
	{ if (! pulse_present && pulse_started) pulse_started = 0;/* Abortive pulse detected, so clear count */\
	  if (pulse_present)										\
	    pulse_present = (pulse_ended++ <= pulse_threshold);	/* Bump end verification count and test */\
	}												\
})

/*********************************************************************************************************/
/* If the motor is enabled, Find the input pulse width count and calculate the scaled PWM output pulse width.
   Motor is enabled here also if the PWM setting is zero.
   Filtering over 8 samples is applied to smooth out noise in the pulse width */

#define COMPUTE_PWM_SETTING ({										\
	if (pulse_width > in_pw_min)				/* If greater than minimum allowed pulse */\
	{ if (motor_enable)				        	/* Only allow changes if motor enabled */\
          { PWM_final_width = (7*PWM_final_width+scale*(pulse_width-in_pw_min))/8; /* Scale and smooth */\
	    if (PWM_final_width > PWM_limit)								\
	      PWM_final_width = PWM_limit;								\
	  }												\
	}												\
	else												\
	{ PWM_final_width = 0;									        \
	  motor_enable = 1;					/* Can safely enable motor having detected motor off*/\
	}												\
})
		
/**********************************************************************************************************/
/* Just turn off the motor */

#define MOTOR_OFF ({										        \
	PWM_final_width = 0;					/* Turn motor off */			\
})

/********************************************************************************************************/
/* Setup PWM counters and set motor control for cases of motor full on, full off or in PWM mode.
   The actual PWM setting is changed gradually until it reaches the computed final speed setting. */

#define INIT_PWM ({											\
	TCCR1B = 0;					        			/* Turn off Timer 1 */			\
	PWM_active = 0;									/* Don't allow PWM processing yet */	\
        if (PWM_final_width > PWM_width)			/* Move PWM setting up towards goal */	\
        {  PWM_width += PWM_step;									\
           if (PWM_final_width < PWM_width)			/* Check if we have jumped past it */	\
             PWM_width = PWM_final_width;								\
        }												\
        else if (PWM_final_width < PWM_width)			/* Move PWM setting down towards goal */\
        {  if (PWM_width < PWM_step)				/* check we don't go below zero*/       \
             PWM_width = 0;								                \
           else												\
             PWM_width -= PWM_step;								        \
           if (PWM_final_width > PWM_width)		        /* Check if we have jumped past it */	\
             PWM_width = PWM_final_width;								\
        }												\
	if (PWM_width == 0)			                /* if shorter than minimum width */     \
	   (PORTB) |= (1 << (PB3));					/* Turn motor fully off */		\
	else if (PWM_width == PWM_limit)			/* else if at upper limit */		\
	    (PORTB) &= ~(1 << (PB3));				/* Turn motor fully on */		\
	else										/* Setup timer for timed PWM cycle */	\
	{   (PORTB) |= (1 << (PB3));		/* Pulse motor off to start */		\
	    PWM_active = 1;							/* If inbetween, allow PWM processing */\
	    TCCR1B = 0x02;				        	/* Configure timer 1 at ck/8 */		\
	    RESET_TIMER_1;							/* Start timer at 0 */			\
	}												\
})

/**********************************************************************************************************/
/* Detailed PWM processing: Check for position in the PWM cycle and execute cycle changes */

#define PWM_ACTIVATE ({											\
	if (PWM_active)				                /* Only do this if PWM mode is appropriate */\
	{   PWM_count = TCNT1;		                /* Read timer to get input pulse width */\
	    if (PWM_on)								/* "On" part of cycle */		\
	    {   if (PWM_count > PWM_width)			/* Check if timer has reached end of "on" cycle */\
		{   (PORTB) |= (1 << (PB3));			/* Turn off motor */			\
		    PWM_on = 0;							/* Advance to "off" part of cycle */	\
		    RESET_TIMER_1;									\
		}											\
	    }												\
	    else											\
	    {   if (PWM_count > PWM_period-PWM_width)		/* Check if timer has reached end of "off" cycle */\
		{   (PORTB) &= ~(1 << (PB3));			/* Turn on motor */			\
		    PWM_on = 1;							/* Advance to "on" part of cycle */	\
		    RESET_TIMER_1;									\
		}											\
	    }												\
	}												\
})

/**********************************************************************************************************/
/* Test over a few cycles to see if the battery has dropped below threshold defined in circuit */

#define CHECK_BATTERY ({										\
	if (bit_is_clear(ACSR,ACO))					/* Battery low detected */		\
	{    if (battery_down++ > battery_threshold)		/* If a number of detections occur */	\
		battery_low = 1;						/* Set battery low variable */		\
	        motor_enable = 0;					/* disable motor to lett bettery recover*/\
		MOTOR_OFF;									        \
	}												\
	else												\
	{   battery_down = 0;						/* reset battery low signal if battery recovers */\
/* Use this code for an automatic soft motor control on battery low. When the battery recovers (after   \
   the motor has been turned off), it will clear the condition, re-enable the motor, but only allow a   \
   slightly lower maximum speed that cannot be increased.  */		                                \
	    if (battery_low && (PWM_limit > 0))			/* If battery has recovered */		\
	    {   battery_low = 0;					/* Clear battery low condition */	\
		motor_enable = 1;						/* Re-enable motor */			\
		PWM_limit -= PWM_period/8;				/* Drop maximum speed */		\
		if (PWM_limit <= 0)						/* If down to zero, stop everything */	\
		{   PWM_limit = 0;									\
	            motor_enable = 0;				/* disable motor */			\
		    MOTOR_OFF;									        \
		}											\
	    }												\
/* Use this code for a hard disable motor control on battery low. It will clear the condition but keep the
   motor disabled. The operator can then try to restart it (by setting the control to zero then up again)\
   if absolutely necessary */			                                                        \
/*	    battery_low = 0;					*/					\
	}												\
})

/*********************************************************************************************************/
/* MAIN PROGRAM */

	unsigned short finished;
						/* Watchdog timer - no need to reset as the macros do this */
						/* Setup the watchdog timer for 30ms timeout */
	wdt_enable(0x9);	
	INIT_HARDWARE;
	INIT_GLOBALS;

/* First we will wait until the input signal is low (inactive) to get to a useful state */

	finished = 0;
	while (! finished)
	  finished = bit_is_clear(PINB,PB4);
	pulse_present = 0;					/* At this stage we now have no pulse present */

/* This is the big loop, testing for input pulse present and also activating PWM cycle and testing flat battery */

	while (1)
	  {   wdt_reset();					/* Reset the watchdog timer */

/* Check the input pulse condition and activate motor changes */

	    TEST_PULSE;									/* Test condition of input signal */
	    if (pulse_present && pulse_started)			/* We have detected a pulse start */
	      { pulse_started = 0;
		INIT_TIMER_0;								/* Start timer 0 counting to get pulse width */
	      }
	    if ((! pulse_present) && pulse_ended)		/* We have detected a pulse end */
	      { pulse_ended = 0;
	        pulse_detected = 1;						/* Indicate a pulse (on-off) has been found */
		(PORTB) |= (1 << (PB2));				/* Turn off PB2 */
		pulse_width = TCNT0;			        /* Get pulse width from timer 0 value */
		if (motor_enable)						/* If enabled, accept any signal over whole range */
		  valid_signal = ((pulse_width > in_pw_min-5) && (pulse_width < in_pw_max+5));
		else					        		/* Wait for minimum control before accepting signal */
		  valid_signal = ((pulse_width > in_pw_min-5) && (pulse_width < in_pw_min+5));
		if (valid_signal)
		  good_signal++;						/* Count sequence of valid signals */
		else
		  good_signal = 0;						/* If a bad one, kill the sequence */
		if (good_signal > 15)					/* We have a continuously valid signal set */
		  { COMPUTE_PWM_SETTING;				/* Determine the PWM parameters for selected speed*/
		    good_signal = 16;					/* Don't allow unlimited increase of this variable */
		  }
	      }

/* Activate the PWM pulse width control if needed. */

	    PWM_ACTIVATE;							/* This actually times the PWM cycle in detail */

/* Infrequent Event Section: Perform some actions infrequently */

	    if (timer++ > timer_count)
	      { timer = 0;

	        INIT_PWM;							/* Setup PWM to run at selected speed */

/* Check the lost-signal counters. This triggers if no signal occurs for a while */

		if (pulse_detected == 0)			/* A proper pulse not detected in the last round */
		  MOTOR_OFF;
		pulse_detected = 0;					/* Reset this to ensure that a lost signal is caught */
		(PORTB) &= ~(1 << (PB2));			/* Turn on PB2 */
		if (good_signal > 15) signal_check = 0;			/* If a good signal, reset lost-signal counter */
		else if (signal_check++ > signal_lost_count)	/* If we think we have lost the signal -- */
		  MOTOR_OFF;

/* Test for battery low. If so, turn off motor and disable. Two options to re-enable - either automatically when
   battery voltage recovers, but to a lower maximum speed, or re-enabled if battery recovers but stays off, requiring
   operator to follow manual process (note when the motor is turned off the battery voltage will increase, so you
   probably can restart the motor, but do it gradually as too much juice will cause the battery voltage to fall
   again and it will cut off the motor). */

		CHECK_BATTERY;

	      }
	  }							/* End of while loop */

/********************************************************************************/
}

i would like to incorporate more than one channel being monitored and controlled possibly even up to 4 , so any ideas are welcome , but as has been mentioned , it may encroach too much into other tasks and then cause even more problems

The code you posted takes an R/C PPM (pulse position modulation) signal as input (the signal an R/C receiver uses to communicate with a servo) and outputs a continuous PWM (pulse width modulation) signal to drive a MOSFET motor controller. The PPM input code uses polling (not interrupts) and thus would be extremely difficult to extend to multiple channels.

What is your experience with AVR programming? C or assembly? Have you developed a project using interrupts before?

Are you trying to output PWM data to drive MOSFETs as this code does or do you want to output PPM to drive regular servos?

Does the AVR need to be doing anything else (driving an LCD or reading other sensors or whatever) in addition to the servo processing?

And to confirm, you are looking for 4 channels in and 4 channels out?

thanks Vraz , the ultimate is to drive a motor and be able to ramp the speed of the motor using pid as they will be a hall sensor monitoring RPM and then not only have speed control from the rc as is usual , but also have the motor under RPM control within a dead band area , hence the thoughts of using pwm , and using the code as a starting block , amongst also using one other rc channel as a start / emergency stop switch

i mostly code in c but it’s my first time with an avr , pwm and interrupts , and i hate assembler .

so a learning curve is in order , we all had to start somewhere , i am just wondering why start here !

thanks for the handholding it is appriciated

oh forgot to add their is also a servo to control too which is connected to a fuel supply valve , i hope then at most theirs only 3 rc input ppm channels to monitor , although only one ( servo ) requires pwm control , if that makes sense

Hi,

Vraz Wrote:

The PPM input code uses polling (not interrupts) and thus would be extremely difficult to extend to multiple channels.>

If I were a rude impolite person I’d say “b.u.l.l.s.h.i.t” but I’m not, so I won’t.

This is polled here: http://homepages.paradise.net.nz/jameskea/

I’m reading in 9 RC channels, 3 analogue piezo gyros and 1 analogue battery monitor, while simultaneously running 3 PID algorithms and driving 4 motors with PWM using an ATMega128.

I’ve actually tapped off the raw serial PPM signal rather than read individual 1-2ms decoded output pins. The main reason for reading the raw PPM is because the receiver’s onboard decoder wasn’t high enough resolution for me.

If you choose to read individual decoded outputs though, the important thing to remember is that you don’t need to read each and every discrete channel output pin. Doesn’t make any sense to me.

If you remember that Ch1 end pulse falls precisely on Ch2 start pulse and Ch3 start pulse is Ch2 end pulse etc. … you therefore don’t need CH2, 4,6,8 etc pins.

I’ll leave the implementation up to you, but save your I/Os and interrupts.

Why use more than you need to ?

In fact, polling gives higher count resolution than using interrupts method.

Interrupts waste clock cycles due to the overhead involved.

ADI