Tuesday, April 21, 2015

AVR PWM Capture of 6 Channel Spektrum RC Receiver

I use the Spektrum receiver out of convienience as I have a Spektrum radio handy. The way the receiver outputs the channels will effect the way I multiplex the signals. When looking at the outputs of the receiver in the Saleae Logic analyzer it can be seen that the channels are not sent at the same time. They are sent sequentially. My device was set up to monitor the channels in numeric order. Channel 0 on the receiver mapping to channel 0 on the analyzer.
Spektrum AR610 Receiver

See the closeup of the receiver to the right. The channels mappings by default in the first setup are:

Channel 0 - THRO
Channel 1 - AILE
Channel 2 - ELEV
Channel 3 - RUDD
Channel 4 - GEAR
Channel 5 - AUX1


Spektrum Receiver Output
Looking at the output of those channels in the logic analyzer we can see how this particular Spektrum receiver outputs the PWM pulses. It would be interesting to test the output of additional receivers to determine how they output. As this is the receiver I will install in my PiHexJ robot there is not incentive to do that now.

At the end of my previous post Raspberry Pi Based Java Hex Robot: PiHexJ Radio Control Intro - The Control Event I has some open questions regarding the processing of the receiver signals by the AVR microprocessor. I outlined my design for multiplexing the signals to take advantage of the AVR microprocessors input capture feature for PWM decoding. I had concerns about how this would operate.

Multiplexing the PWM signals from the AR610 Spektrum Receiver

The test bed is shown in the picture below. I am using a 74HC4051 8 Channel Multiplexer to multiplex the Spektrum AR610 6 channel receiver. The questions to address are will there be issues with overlapping signals causing incorrect results on switching and what will the latency be.

Test Setup for RC Receiver Multiplexing
74HC4051 Pin Description
The multiplexer uses uses three pins to switch the multiplexer input to the the output pin (Z). The input pins high low like a binary counter. By using pins 0-2 on PORTD on PORTD I can use a counter to count between the input channels. The device supports 8 channels I am using 6. By setting the PORTD ouput I can switch the input to the input capture pin (14 - ICP1)

74HC4051 Function Table
AVR Mega 328 pin diagram
I am using PORTD pins on my AVR microprocessor to drive the multiplexer. PORTD is convenient as it still allows the programmer to connected and keeps the I2C (SDA and SCL) pins free.

My Pin mappings are:
AVRMultiplexer
PD0S0
PD1S1
PD2S2
PD3E
PB0Z
And PD4 is used to connect to the raspberry pi to signal when the control input has changed.

For the AVR firmware C code check out my github the file is in the hexfirmware repository https://github.com/margic/hexfirmware/blob/master/Firmware/firmware.c rather than the main PiHexJ repository. The AVR is initialized with the input capture interrupt enabled. The following code is triggered when the ICR interrupt is fired. It grabs the value from the timer register and stores it in an array channels[]. The index, plex, is then updated. The value is compared and if changed the update count is updated. I do this so I don't trigger updates to the raspberry pi if no input changed have occurred. PORTD pin PIEVENT is turned on to signal the pi to read the channels via I2C as per my design. PIEVENT corresponds to pin PD4. 

Finally the multiplexer is stepped to the next input.

ISR(TIMER1_CAPT_vect){
	// Input capture interrupt handler
	// first time is triggered in leading edge
	if(PINB & (0x01)){ // pin high 
		// clear counter and set to trailing edge
		TCNT1 = 0;
		TCCR1B &= ~(1<<ICES1);
	}else{
		// get counter from ICR1
		// reset to look for leading edge
		uint_fast16_t counter = ICR1 + 47; // add some counts for processor over head //
		if(channels[plex] != counter){
			updatedCount += 1;
		}
		channels[plex] = counter; 
		
		if(plex == NUMCHANNELS - 1){
			if(updatedCount != 0){
				PORTD |= (1 << PIEVENT);
			}
			updatedCount = 0;
			plex = 0;
		}else{
			plex += 1;
		}
		switchMultiplex();
		TCCR1B |= (1<<ICES1);  
	}
}

void switchMultiplex(){
	uint8_t temp = PORTD & 0xF0;
	PORTD = temp | plex;
}
The code works as intended. The following diagram shows the output on channel 6 of the logic analyzer. 
Multiplexed Signal at AVR ICR Pin Channel 6
While it works it does take over 51 ms to get all the channels switched to the avr. This is due to the way the capture has to wait for the leading edge of the signals. I experimented with changing the order of the input to the multiplexer to optimize the way the AVR reads the signals. This looked promising when reordering the channels in the Saleae Logic Analyzer.

Optimized Receiver Channels
When optimized this way the total time is reduced to 10.5 ms. This looked promising so I ran a test using the multiplexer to see if it could catch the transitions. In theory it should as the switch time of the multiplexer is rated at 5 nano seconds with a max of 12 nano seconds. In practice however....

Failed Multiplexing Channel 6
In practice it did not work. The processor overhead is too much and channel six above shows that the pulses are not being multiplexed correctly. I tried ordering the input channels staggered to avoid this and still gain. This final output blow looks more promising.


The output of all 6 channels are being sent to the AVR and measured. Shown on channel 6 above. The total time is 31.7ms. While not as quick as the optimal, it does work and it is still faster than over 50ms. This will be the layout I used for the initial RC control of the robot.

Conclusion

There will be additional latency as the Raspberry Pi will read the values from the AVR controller to determine the values the receiver is receiving from the radio. In other applications this would not be acceptable. However in this application a estimated 50ms latency before the robot responds should negligible. This can be revisited if it proves to be a problem.

Next Steps

The next tasks are to make the Raspberry Pi respond to the AVR signal (PD4 going high) and have it request the values via I2C. I am currently developing the simulator file that will test the AVR code. I will publish it when complete.

2 comments: