Rpi based quadcopter with 2.4 radio control

Hello,

I am starting to build quadcopter based on RaspberryPi. So far I have figured out that it is possible to control ESC for brushless motors just using I2C, without any additional microcontrollers(my goal is to avoid Arduino if its possible) - or I am wrong? :) . First I am going to control it by WiFi and phone/tablet but I know that range won't be so long as I want it to be. That's why I want to use classic radio control like this one http://www.hobbyking.com/hobbyking/stor ... 47080.html . The question that I found really hard to get answer for it is - can I connect this receiver directly to raspberrypi and pass commands from controller to ESC?(ofcourse trough some script that will handle stabilization etc)
I think that this is the best forum to ask this question :) Also I want to use XloBorg for flight stabilization and movement orientation.

Thank you very much in advance for an answer

piborg's picture

The link you provided is incomplete (has a ... in it) so I will go on what I know instead.

The servo signal you would need to read into the Raspberry Pi is a PWM signal, not an I2C signal.

You can connect the signals through the Raspberry Pi in the way you want, but you will need to make sure the signals are 3.3v, otherwise the Raspberry Pi GPIO will not work with them.

I think it is likely that the servo signal will be a 5v level, therefore you will want a board for the Raspberry Pi which allows you to both read and output 5v levels instead.
For example the following board can convert from 5v to 3.3v:
https://www.modmypi.com/adafruit-4-channel-i2c-bi-directional-logic-leve...

Connecting 5v signals directly to the Raspberry Pis GPIO without a converter board will most likely damage the Raspberry Pi permanently!

You will want to connect the signal wire from the receiver to one GPIO pin, and the signal wire to the ESC to another GPIO pin.

You can read the PWM signal by using the pigpio library with either the callback or wait_for_edge functions.
pigpio: http://abyz.co.uk/rpi/pigpio/download.html
Python notes: http://abyz.co.uk/rpi/pigpio/python.html
callback function: http://abyz.co.uk/rpi/pigpio/python.html#callback
wait_for_edge function: http://abyz.co.uk/rpi/pigpio/python.html#wait_for_edge

What you want to do is time between:

  • Rising edge to falling edge
    this is the high time
  • Falling edge to rising edge
    this is the low time

You can then calculate the input PWM duty cycle as:
dutyIn = high_time / (high_time + low_time)

This duty cycle tells you how much power the receiver wants the ESC to apply.

To control the ESC you then set the duty cycle on your other GPIO pin.
You can do this with the set_PWM_dutycycle from the pigpio library:
http://abyz.co.uk/rpi/pigpio/python.html#set_PWM_dutycycle

You can convert the duty above to the correct range like so:
dutyOut = int(dutyIn * 255)
and then set the duty cycle like so:
pi.set_PWM_dutycycle(4, dutyOut)

Hopefully that is enough information to get you started ^_^

Woow, that's way more than I've expected, thank you so much :)
Apparently forum has messed up my link - here is correct one. As you predicted Rx voltage is between 4V and 6V so I must lower voltage(and buy 2 converters, because it has 4ch).
It is good news anyway because now I think that I don't need to hesitate buying this controller :)

Altough, during research in the internet I've found little bit different approach - connecting radio receiver to rPi using Arduino micro trough the I2C. That's because raspberryPi isn't accurate enough to precise read joystick values(freeing Rpis resources a little bit is another thing).
Altough, another device like Arduino consume more power, which is very limited on flying vehicle...
What do you think, which approach would give better results?

Thank you again for this awesome support!

P.S. I'm starting to understand why quadcopters are so expensive... ;)

piborg's picture

I would expect using the Arduino would give better results.

As you said the Raspberry Pi is not going to be the most accurate for timing pins, the Arduino however is well suited for that kind of task.

I would imagine the power drain from the Arduino would be significantly smaller than the Raspberry Pi.
The bigger problem is really the power drain caused by the motors themselves.

Also here is a link for an 8-channel converter since you need more pins:
https://www.modmypi.com/adafruit-8-channel-bi-directional-logic-level-co...

Yeah, I've also finded this 8channel one, thank you :) Well my goal was to avoid Arduino if it is possible, so I've ordered level converter and I will try to build something using just RaspberryPi. Happily Arduino and level converter are quite cheap so I can try and experiment :)
Thank you again, I will show you when I will succeed to build something that works and fly :)

Hello,
I've received my transmitter and receiver. Receiver needs 5-6V to power but when I measured voltage on signal pin multimeter shows voltage between 0.22V and 0.49V... I know that it is probably stupid question but it really can be this low and I don't need this level shifter or I cannot measure PWM voltage just by multimeter? :-) I would be very glad if you could help me to understand that :-)
Thank you in advance!

piborg's picture

The simple answer is that looking at a PWM signal pin with a multimeter will usually show the average voltage sent by the pin.

The whole answer is that the length of time the signal is high and low is the important thing, simply measuring the average is not enough to see what the signal is.
It is likely that the signal pin is at the 5-6v level for a brief period, then it is at 0v for the remaining time (explaining the low average).

You will need the level shifter still as the brief bursts of power are still potentially enough to damage the Raspberry Pi.
You also need the level shifter on the output as the ESC will need to pulse 5v bursts, the Raspberry Pi will only provide 3.3v bursts from the GPIO.

I would recommend watching this video for a quick explanation of what PWM does and how it works:
https://www.youtube.com/watch?v=Lf7JJAAZxEU

Well, I've obviously had some lack of fundamentals... ;) Thank you very much, that cleared up a lot for me! :)

I've succeed to connect all stuff to Pi, pigpio library is reading rising and falling edges, but - I cannot notice any change of PWM signal while moving joystick in transmitter.

Could you please be so awesome to look at that code and give me some hint is it correct? :)

#!/usr/bin/python
import pigpio
import time

pi = pigpio.pi()

#defining variables to avoid errors with math at start

rising = 0
falling = 0
lowTime = 0

#infinite while loop to catch PWM signal and count duty cycle

while(1):
    if pi.wait_for_edge(25):
        rising = time.time()
        if falling != 0:
            lowTime = rising-falling
    
    if pi.wait_for_edge(25, pigpio.FALLING_EDGE, 5.0):
        falling = time.time()
        highTime = falling-rising
    dutyIn = highTime / (highTime + lowTime)
    print dutyIn
#    dutyOut = int(dutyIn * 255)
#    if dutyOut < 121 or dutyOut > 129:
#        print dutyOut

It all the times gives me value about 0.496645195921
or value about 125 after convert to dutyOut (it fluctuates a little all the time).

Thank you very very much in advance! :)

piborg's picture

I would have expected that to work, however I do have some thoughts on how we can get some better information on the problem.

If you could try the following code:

#!/usr/bin/python
import pigpio
import time
 
pi = pigpio.pi()
 
#defining variables to avoid errors with math at start
 
rising = 0
falling = 0
lowTime = 0
 
#infinite while loop to catch PWM signal and count duty cycle
 
while(1):
    if pi.wait_for_edge(25):
        rising1 = time.time()

    if pi.wait_for_edge(25, pigpio.FALLING_EDGE, 5.0):
        falling = time.time()

    if pi.wait_for_edge(25):
        rising2 = time.time()

    highTime = falling - rising1
    lowTime = rising2 - falling
    dutyIn = highTime / (highTime + lowTime)

    print dutyIn
    print highTime
    print lowTime
    print highTime + lowTime
    print '---'

This will miss some of the pulses but should give a clearer idea of what is going on.

Firstly we are not timing the print statement anymore (it may cause delays which affect the result).
Secondly we are showing some more data about what the pulses look like.

If the signal is a conventional PWM signal, the fourth number (highTime + lowTime) should be approximately constant.
If it is not then either:

  1. We are missing some edges for that pulse (RPi not keeping up?)
  2. The code is not behaving as expected
  3. The incoming signal is not a simple PWM like I thought it was

This code will unlikely fix the problem, but it might make it clear what the problem is.

Ok, so I ran your code and now the question is how approximately is approximately ;)
this is sample output when joystick is idle:

0.501410775858
0.0522830486298
0.0519888401031
0.104271888733
---
0.500401716425
0.0522699356079
0.0521860122681
0.104455947876
---
0.501489845079
0.052365064621
0.0520539283752
0.104418992996
---
0.49917093002
0.0521080493927
0.0522811412811
0.104389190674
---
0.500952096907
0.0520598888397
0.0518620014191
0.103921890259
---
0.499612396896
0.0520901679993
0.0521709918976
0.104261159897
---
0.500820220919
0.0518980026245
0.0517280101776
0.103626012802
---
0.497994095311
0.0519990921021
0.0524179935455
0.104417085648
---

and here is the output when joystick is moved to another position:

0.500880177312
0.0523710250854
0.0521869659424
0.104557991028
---
0.502921860706
0.0526921749115
0.0520799160004
0.104772090912
---
0.500981718225
0.0520129203796
0.0518090724945
0.103821992874
---
0.497412664579
0.0519089698792
0.0524489879608
0.10435795784
---
0.500351619056
0.0519080162048
0.0518350601196
0.103743076324
---

So the high time + low time is more or less the same, but maybe this difference is too big anyway?

I've checked on multimeter and there I can see the difference when moving josticks. The only thing that looks weird to me(but again - it may be again just my lack of fundamental knowledge in some parts ;) ) that before the level shifter multimeter shows between 0.22V and 0.49V and after the converter it shows between 1.40V and 1.60V. But anyway, at least I know that level shifter works and Pi should obtain some information about signal change...

Thank you again for you help!
Cheers.

piborg's picture

We are not really sure what to make of these values, but they are not quite what we are expecting.

Could you upload some photos of your wiring, it may shed some light on what is going on here.

Sure, below you can find photos, I hope you can see what you want.

Receiver:

I've connected external power source from 4x AA rechargable batteries(5V output from Pi causes freezing, so I must provide it other way) - pin 2 and 3 on 1st channel.
Signal pins from 1-6 - first pin of every channel.

Level shifter:

- VCCB and GND - positive and negative from receiver's power source
- B1 to B6 - signal pins from receiver
- VCCA 3.3V output power from Pi
- according to converter's documentation OE should stay low to enable output
- A1-A6 connections to GPIO pins on Pi(currently only 1st and 5th)

Accelerometer and altitude meter are connected trough I2C to SDA, SCL and Pi 3.3v power so it shouldn't interfere anything, it was easier for me to connect all wiring at once :)

I am really corious what I've made wrong that it works like that... But other parts for quadcopter are still in shipping so at least I'm not so frustrated that I've stucked :P

Images: 
piborg's picture

I had a thought this morning, you should probably be setting the GPIO pins to input mode before using them with wait_for_edge.

Try the following version of code and see if the behaviour is any different:

#!/usr/bin/python
import pigpio
import time
 
pi = pigpio.pi()
 
#defining variables to avoid errors with math at start
 
rising = 0
falling = 0
lowTime = 0

#set the GPIO pins to be inputs
pi.set_mode(25, pigpio.INPUT)
 
#infinite while loop to catch PWM signal and count duty cycle
 
while(1):
    if pi.wait_for_edge(25):
        rising1 = time.time()

    if pi.wait_for_edge(25, pigpio.FALLING_EDGE, 5.0):
        falling = time.time()

    if pi.wait_for_edge(25):
        rising2 = time.time()

    highTime = falling - rising1
    lowTime = rising2 - falling
    dutyIn = highTime / (highTime + lowTime)

    print dutyIn
    print highTime
    print lowTime
    print highTime + lowTime
    print '---'

Let us know if this changes the behaviour, in the meantime we will carry on looking at the wiring to see if we can spot any problems.

Hi,

I also thought that it was it but unfortunately it didn't change anything, still the same values without any changes. I don't know, maybe Pi is too slow to get PWM like that?

It might be worth looking up 'EVENTS AND CALLBACK FUNCTIONS' to see if this gives you better results. Polling the GPIO is fine for none time critical tasks but CALLBACKs are better if you need more speedy responses. Keep your callback routines short an sweet. Do all of your heavy duty processing in the main loop and only add bounce if you find you need it in this instance.

Suggestion for CALLBACK inputs. Not tried it myself but seem sensible enough;

def someSpeedyFunction1(channel):
	saveUpTime = time.time()

def someSpeedyFunction2(channel):
	saveDnTime = time.time()

channel = 23
GPIO.add_event_detect(channel, GPIO.RISING, callback=someSpeedyFunction1, bouncetime=4)
GPIO.add_event_detect(channel, GPIO.FALLING, callback=someSpeedyFunction2, bouncetime=4)

If you still have no joy then maybe the Pi cant process fast enough. If this is the case extra hardware would be required using I2C maybe. I'm sure you'll run into similar timing problems going the other way too, e.g. PWM out, I use the 'Adafruit PWM/Servo Driver - 16 Channel 12 BIT (PCA9685)' for this reason and it works extremely well.

Hi!
As you probably figure out I didn't suceed reading PWM signal directly by Pi. According to your advice I have bought PCA9685 to handle PWM signal both ways(since there are 16channel and I will use 6 input and 4 output). But as long I can find a lot of info how to set PWM signal, I have failed to find any information how to read signal from receiver. Can I ask you to provide me some guides how to get the signal ? :)

Thank you in advance!

HI albertc180,

It worked for me. I followed Ginger Adams suggestion of using a callback function and it finally worked. Here I post my code, if it is of any use. All the best.

#!/usr/bin/python
import time
import RPi.GPIO as GPIO

#defining variables to avoid errors with math at start
  
lowTime = 0
saveUpTime_old = 0.2
saveUpTime_new = 0
saveDnTime = 0.1
PwmInputPin = 16
channel = 16
counter = 1

# Initial setup

GPIO.setmode(GPIO.BCM)
GPIO.setup(PwmInputPin, GPIO.IN)

# Define a threaded callback function to run in another thread when events are detected  
def my_callback(channel):  
    if GPIO.input(PwmInputPin):     # if port PwmInputPin == 1  
        global GPIO_triggered, saveUpTime_new, saveUpTime_old, highTime, lowTime, dutyIn, saveDnTime
        # print ('actual time', time.time(), '  :   saveUpTime_old', saveUpTime_old, '  :   saveUpTime_new', saveUpTime_new) 
        saveUpTime_old = saveUpTime_new
        saveUpTime_new = time.time()    # print "Rising edge detected on PwmInputPin"
        highTime = saveDnTime - saveUpTime_old
        lowTime = saveUpTime_new - saveDnTime
        dutyIn = highTime / (highTime + lowTime)
        GPIO_triggered = 1
    else:                  # if port PwmInputPin != 1
        # global saveDnTime
        saveDnTime = time.time()    
        # print ('Falling edge detected on PwmInputPin')

GPIO_triggered = False

# when a changing edge is detected on port PwmInputPin, regardless of whatever   
# else is happening in the program, the function my_callback will be run  
GPIO.add_event_detect(PwmInputPin, GPIO.BOTH, callback=my_callback)

while(counter < 10):
    while not GPIO_triggered and counter < 10:
        time.sleep(1)

    GPIO_triggered = False
 
    print (dutyIn)
    print (highTime)
    print (lowTime)
    print (highTime + lowTime)
    print ('---')
    counter = counter + 1


# Cleaning USED pins before exiting
GPIO.cleanup()

piborg's picture

We are a bit stumped as to what the problem actually is.

What we intend to do is setup a signal generator, attach it to the Raspberry Pi pin, and check if the code works as expected when we ensure the input is what we expect it to be.

Unfortunately we are a bit busy at the moment, it will probably take us a few days to find the time to test it here.

We will let you know what we find out.

Sure! I am really really grateful and so pleasantly surprised that you are so involved in this mine problem. Thanks again! :-) meanwhile I will try the @Ginger suggestion, I will let you know if I will figure out something eventually :-)

Ey man! I'm so glad I came across this blog! I am building a quadcopter with a raspberry pi b+, but now I'm kinda stuck with reading the PWM from the RC receiver (which I haven't bought yet, gotta make sure how to work around this problem then I'll buy it).
I think I was on the same page as you when it came to the fundamentals, for I didn't even know what PWM was. I have learned a lot and this is a lot of fun, but like I said, I'm stuck now. I realized that I have to buy a separate microcontroller to deal with more precise PWM signals (I was really trying to avoid this, which is why I switched to C++ for more precision). So, I guess my question is, was the pca9685 worth it? Does that have a I2C output too? I need it cause that's what I'm using to get the info from the Gyro/Accelerometer .

Hi!
Well, you are wiser than me, cause you are making better research first. Unfortunately PCA9685 is only OUTPUT device :( You can check my question directly at adafruit's forum. You can use it to send PWM signal to ESCs(and as far I discovered it is quite recommended that way) but you cannot use it to get signal from transmitter. The only solution that was left for me is a simple Arduino mini(could be cheap one from ebay) but unfortunately I don't have any time to work further on my project, it is still in parts ;)
But I have built quadcopter on built flight controller(first KK2.1.5, now Naza M Lite) and I must say - it is good to start with that to get familiar with multicopter way of working/flying. KK2.1.5 is quite cheap and you can understand how all of it works(especially PIDs, motors and propellers setup etc) :) I don't thing that I could made my own made quadcopter flight without any of this knowledge ;)
I would be glad if you could let me know when you will solve the transmitter input case :)
Cheers!

The PCA9685 is very definitely PWM out only. If you are using a DSMX/2 TX you could investigate the use of a satellite RX like the Lemon DSMX Satallite RX as this decodes the DSM signal from your TX into a data stream that can be connected directly to the Pis UART RX and read as data bytes would need to research the protocol though to understand what the bytes mean, though I'm sure its well documented (somewhere). Just ordered mine. Much more friendly. You'll still need the PCA9685 to drive servos and motors etc... ;o)

Subscribe to Comments for &quot;Rpi based quadcopter with 2.4 radio control&quot;