DIY: Receiving the PPM signals from RC receivers

Yes, I know I jump around a lot… But I have seen enough comments up on the forums of people wanting to have better control than they can get from the PS2 and don’t want to go to the complete job of building the DIY remote and by chance may have an RC Transmitter/receiver laying around. The problem up till now was the amount of overhead to the system that reading in the 6-7 PPM channels was causing.

Why I thought of it today is while looking for something else my receiver fell out of the cupboard. So I did a little hunting around the web and found that it would not be hard to hack the receiver… I tried several searches, but the best one I found was “Arduino PPM”. I looked at a few sites and decided it might not be hard. So I removed the receiver from the plastic housing and turned it over and found a shift out register. A quick look at the data-sheet showed that Pin 1 was a clock pin. So I hooked up my logic analyzer with a pin for each of the output PPMs and one on this clock pin. Mine looks like:

I then plugged in a power/ground line to connect the receiver up to my BB2 such that the receiver had power and then turned on the transmitter and then did a logic scan so see what I got. Here is a sample output.


As you can see the one clock pin nicely clocks out each of the signals. For the fun of it I will probably try to solder on a test lead to bring this to the BB2 and write some code to receive this data. Let me know if anyone else is interested in this. Note the top line in my trace was for Ch7 and I have a Laser6 so it appears like it is high for the entire time between cycles…

Kurt

P.S. - Many of the examples I saw up on the web were for Futaba receivers.

Hi Kurt,

Yes, it’s interesting! In some ways, this problem sounds much like recovering the spark plug firings from a car engine. If you have a “sync” to the #1 plug, then a single line to the cap will give you all of the plug’s firings. A/D and software take it from there to split up the individual plugs.

I don’t have a Laser 6 anymore (just developed a 'bot for a friend with it), but I have some old 4-channel R/C to play with. If it was really promising for my use, I’d not hesitate to get an up-to-date R/C.

The limited number of channels in the “frame” are probably sufficient to allow you to extract the “#1 plug” timing.

Alan KM6VV

Yep it should be easy to resync. If the delta time is > than something like 2500 we know that we just measured the pause between RC frames so the next should be channel 1… Will try soldering in later tonight or tomorrow morning and write up some code. Will start with something simple like test program, than may build a Phoenix V2.x RC version. Will set up an interrupt pin and let it run in background always filling in array of pulse widths. Will disable interrupt when we do serouts to SSC as to not have interrupts screw it up.

Pluses are: Should minimize system overhead. Only one IO pin required.

Should be fun.
Kurt

Only one I/O pin, and only one data stream to process!

What board (ARC / BB2) is this for? Both I hope!

Alan KM6VV

Yep, should work well for both boards :slight_smile: . Will play with in the morning. s

Kurt

Quick update, did not get as much done today as I wanted. We spent most of the day taking a drive…

I have soldered in wire to the point I mentioned that has a female plug on it that I then plugged into my BAP28 pin 19 (IRQ2).
I have started a test program, that I cut and pasted the TimerA code from the phoenix 2.x code. I made changes to it to currently increment TMA on clock/128 instead of clock/8192 or about 8us resolution. If I find that resolution not sufficient may convert to clock/32 instead or 2us. I am thinking the 8us may be sufficient as if our servos go from lets say 500-2500us, that is a delta of 2000us/8us gives us 250 values which is similar to what we are processing from the DIY… But will experiment.

I started off writing the interrupt handler in assembly code, but that I would first try things out in basic and then finish the assembly language. So far the test version of the interrupt handler is looking like:

[code]Handle_IRQ2:
toggle p4
;gosub GetCurrentTime - don’t want to enable interrupts yet so do inline
_lNewPulseTime = lTimerCnt + TCA
; see if timer is waiting to process interrupt.
if IRR1.bit6 then
_lNewPulseTime = lTimerCnt + 256 + TCA
endif

; calculate the pulse width and convert to us and save away the pulse time 
_wPulseWidth = _lNewPulseTime - _lLastPulseTime		; not scaled yet

#ifdef DEBUG
if cRaw < 100 then
awPPMsRaw(cRaw) = _wPulseWidth
cRaw = cRaw + 1
endif
#endif
_wPulseWidth = (_wPulseWidth * (WTIMERTICSPERMSMUL * 1000)) / WTIMERTICSPERMSDIV
_lLastPulseTIme = _lNewPulseTime;

; See if the value appears to be OK.
if _wPulseWidth > 2500 then 
	; Where were we??? 
	if _bNextChannel < CNT_PPM_SIGNALS then
		; We got a long value when we did not expect it...???
		fPPMsValid = 0;
	endif
	_bNextChannel = 0; 	// Ok lets assume the next value will be for our first channel
else
	if _bNextChannel < CNT_PPM_SIGNALS then
		awPPMs(_bNextChannel) = _wPulseWidth
		_bNextChannel = _bNextChannel + 1
		if  _bNextChannel = CNT_PPM_SIGNALS then
			fPPMsValid = 1		; Let the main line know that we received a complete valid set of values
		endif
	endif
endif
	
resume

[/code]

I have some simple main line code that simply checks to see if any of the values change and if so copy them to another array and print out the array… I am also printing out if I think the data is valid.
I am starting to get valid results…

1 1392 1552 1448 1576 1104 1920 1 1400 1552 1440 1584 1096 1920 1 1400 1552 1448 1576 1096 1920 1 1400 1552 1440 1576 1104 1912

Will experiment more tomorrow. Will play with different timer values and maybe make an Arc32/Bap40 version. This will be nice as I can user HSEROUT to output the debug information. Or maybe I will make a RC version of an input method for the phoenix…

Kurt

Kurt

Awesome work! 8)

Hey guys,
I just bought the laser 6 over the summer and was trying to figure out how to get the atom 28 to read the ppm signals too. I tried out the code kurte post but i’m getting a lot of errors when compiling. Has this code been debugged yet?

bane

Hi,

I have not played with this much lately, been doing other stuff instead (as I do have the DIY remote control with XBees…), but plan to soon. I have most of a Control unit for the phoenix written but not tested yet. I was planning to do it on the Arc32, but may put it on my CHR-3 with the Bap28 instead… Hopefully soon.
Kurt

Hi Kurt,

Wow, I can’t wait to how your DIY rc comes out (perhaps a future product for sale?) After doing some research on getting the atom to read PPM signals i realize it is much more complicated than i expected. I was hopping to use a simple pulsin function and get a number. Just out of curiosity, do you have any sample code that will take a single channel, read it, and set it equal to a variable?

Bane

Hi Bane,
I do have a lot of fun with the DIY stuff. I wish Robot Dude would package a lot of this into a kit, but there are a few of us now using it.

You can use pulsin command to read in one channel. If you wish to read in multiple channels, you can issue one for each channel. The order you issue these commands will greatly influence the speed of it… To improve on this earlier I wrote assembly language to read in multiple channels in one RC cycle. There are several threads on this including: viewtopic.php?f=8&t=6079&hilit=pulsein7

Will hopefully soon have a hacked up version of this stuff working. My current experiment for Bap28 only is to use IO pin 12 which also is an Input capture pin for TimerW…

More later
Kurt

your level of program is out of the charts compared to mine lol. I just wrote some simple code after consulting the atom programming manual in an attempt to figure out whats going on. This code is supposed to read a single channel and display its reading. So far all it displays though are (what appears to be random) numbers between 0 and 17

is this even close to reading a ppm signal?

[code]
rcinput var word
rcch1 con p6

main
pulsin rcch1,0,rcinput

serout s_out,i9600,[dec rcinput,13]

pause 300

goto main[/code]

thnx, bane

Hi Bane,
Thanks I have had many (more than I would like to admit) years of experience :laughing:

I assume from your program that you have at least one of the channels plugged in from your receiver to the BB2/BAP and that one of them is on Pin 6. Preferably one of the channels 1-4 which are to the joysticks.

Also I am not sure, but you may need to have:

pulsin rcch1, 1, rcinput

That is I think you want to measure it starting at the transition when it goes from low to high. Could be wrong.
Kurt

After the last posts I have made some progress on this. I have been playing with a Bap28 only version of this, that uses the input capture for one of the channels on TimerW. I decided to try this way as I have not tried the input capture and thought it would be fun and it should be reasonably accurate. I started off with a Basic interrupt handler but was having trouble getting reasonable results, so I converted to assembly language. I think I now have the interrupt handler working OK. So I am now trying a setup a simple set of functions for controlling the phoenix. Having it interrupt based, I had to generalize the disable interrupts function I had for the XBEE interrupts such that the serouts to the SSC-32 would work.

I am now playing with how/when to get the inputs. When I disable the inputs I invalidate the data and only after a complete new pass to I say the data is valid. Currently I disable the input through the calls to the ServoDriver and more or less call off to ControlInput before it had a chance to do a complete new pass. I have a couple of options. I could simply wait a certain amount of time for the data to become valid or I could detect if I had new valid data just before the servo driver commit and use it. I will probably go the later route. The data should be very current anyway and it would mean that the input processing should not slow down the code much at all. Can minimize this to only have the code running while in the sleep waiting for the current step to complete. Just in case anyone wishes to see the interrupt handler :laughing:

[code];------------------------------------------------------------------------------
; HANDLE_TIMERW_IMIED handler - The idea is to capture our current time
; value when the interrupt happens and then calculate a delta from the
; previous one. If > some max we know that we are starting up a new run
; of timings, else save away in the appropriate index value in our array
; of timing with 1 per logical channel.
;------------------------------------------------------------------------------
BEGINASMSUB
HANDLE_TIMERW_ASM:
ASM {
; First see why we woke up.
push.l er0
push.l er1 ; save away the registers we are using
#ifdef DEBUG_LA
; bset #4, @PCR5:8 ; make sure it is set for output
; bnot #4, @PDR5:8 ; DEBUG - togle P4…
#endif
bld #3, @TSRW:8 ; See if GRD match…
bcc _RCHTW_CLEANUP:8 ; not ours
bclr #3, @TSRW:8 ; clear it out.
mov.w @GRD:16, e1 ; Get the new GRD value
mov.w @_WGRDPREV:16, r1 ; get the saved value to use
mov.w e1, @_WGRDPREV:16 ; Save away the new one
sub.w r1, e1 ; subtract the prev from the new to get delta - I think it handles wrap OK…
bcc _RCHTW_NGRD:8 ; NO Carry so assume no wrap.
bld #7, @TSRW ; see if it shows a wrap happened
bcc _RCHTW_NGRD:8 ; Did not have overflow so fine
mov.w #0xffff, e1 ; set high value …
_RCHTW_NGRD:
shlr.w e1 ; divide by 2 to get uS
xor.w r0,r0 ; zero out r0
mov.b @_BNEXTCHANNEL:16, r0l ; Load which channel we are using into r0l
cmp.b #CNT_PPM_SIGNALS+1, r0l ; See if this is the first interrupt …
beq _RCHTW_INITMODE:8 ; we are in the init phase…
cmp.w #499,e1 ; see if the value is lower than expected
bls _RCHTW_LESS500:8 ; < 500 assume an error again

cmp.w	#2500, e1				; see if > 2500
bls		_RCHTW_LT2500:8			; 
; So exceeds what we think should be a valid signal
cmp.b	#CNT_PPM_SIGNALS-1, r0l	; 
bhi		_RCHTW_SARTNEWRUN:8	; 

_RCHTW_LESS500:
mov.b r0h, @FPPMSVALID:16 ; say we are not valid r0h should be zero
; bra _RCHTW_CLEANUP:8 ; go to cleanup
#ifdef DEBUG_LA
; bnot #7, @PDR5:8 ; P7…
#endif
_RCHTW_INITMODE:
mov.b #CNT_PPM_SIGNALS, r0h ; Hack - reuse code below to set channel value…
_RCHTW_SARTNEWRUN:
#ifdef DEBUG_LA
bnot #5, @PDR5:8 ; DEBUG - togle P5…
#endif
mov.b r0h, @_BNEXTCHANNEL:16 ; next one should be channel 0
bra _RCHTW_CLEANUP:8 ; go to cleanup

_RCHTW_LT2500:
cmp.b #CNT_PPM_SIGNALS-1, r0l ; see if we are working on a proper channel
bhi _RCHTW_CLEANUP:8 ;
mov.b r0l, r1l ; save the value
extu r0 ;
shll.w r0 ; *2
mov.w e1,@(AWPPMSINPUT, er0) ; save away the new pulse width into the array
inc.b r1l ; increment our counter
mov.b r1l,@_BNEXTCHANNEL:16 ; save away the updated count
cmp.b #CNT_PPM_SIGNALS, r1l ; see if we got a complete set
bne _RCHTW_CLEANUP:8
mov.b #1, r0l
mov.b r0l, @FPPMSVALID:16 ; let caller know we have a complete set
#ifdef DEBUG_LA
; bnot #6, @PDR5:8 ; DEBUG - togle P6…
#endif
_RCHTW_CLEANUP:
pop.l er1 ; restore the registers we used
pop.l er0
rte ; return from the exception.
}
ENDASMSUB
[/code]
Kurt

Minor road block… I was testing on my test board which was an ABB, but decided to try on my CHR-3 with BB2. No longer works. Why? BB2 has pull-up resistor on P12 for PS2 controller, so signal always high, Hitec receiver outputs nothing as the clock pulses don’t work. Hopefully no big issue. Will move to P10 or P11 and move the SSC-32 communication pins…

Kurt

I’ve been trying some of your code but with no luck. I believe your using Bap28 and i only have the Ba28. If im correct there is a difference in programming language right?

Bane

The Atom has 1us resolution for the pulseout command, The Atom Pro has 0.5uS resolution. So the math change is trivial. They both use 1uS for the pulsin command. Hope this helps.

I think I have it working reasonably well now. I am using the rotary switch on the top right of the Laser 6 to choose which mode: You can choose: walk, translate, rotate, and single leg mode. In Walk mode changing the switch on the top left marked Gear, walks through the different gaits, in single leg mode it allows you to change which leg. I have not put in anything yet to turn on/off balance mode, but could probably use the switch in one of the other two modes (Translate, rotate). I also have not put anything in to run a sequence…

Code was setup such that the line I soldered into the Hitec receiver plugs into Pin10 on the Bap. I moved IO lines going to the SSC-32 down one pin 11 and 12. Again this version will only work with Bap28 as it uses TimerW.

Not sure how much more I will do with it as I only did this as an experiment. In case anyone wishes to play with it, I did upload a zip file with all of the files…

Kurt
Hacked RC phoenix.zip (45.8 KB)

Kurte

I think your assembly should be placed within the basic micro studio as an basic command!
perhaps replace the built in pulsin command with like KurteIn or something highly configurable in basic… NOT ASSEMBLY “HEHEH” to read from 1 to 7 or more channels in 1 or 2 passes or 3 passes??

Personally I think it would be cool to have the assembly routines do 1-2 passes before providing an output then output all channels, do another pass and if the data for another channel isnt captured provide old data and just read it on the next pass, so the routines arent waiting around they just provide old data and when new data is available update it.

Hope this makes sense.

–Aaron

Thanks for redirecting me here Kurt.
Im hoping that you might ‘at some point’ make a tutorial on this showing the receiver hack and hookup. im one of those people that cant deny that images speak a thousand words.

looking at the codes it would seem you have used the XBee?
an inventory list of all the parts needed and tutorial would be great!

ill leave you to your current project as i dont want to take up your time. but ill keep a lookout for future post on this.
thanks again Kurt.
J