├── img ├── rpi.jpg ├── rx_rpi.png ├── receiver.jpg ├── main_output.png ├── rpi_config0.png ├── rpi_config1.png ├── rpi_config2.png ├── rpi_config3.png ├── transmitter.jpg ├── complete_build.jpg └── flight_control_board.jpg ├── schematic └── rx_rpi.fzz ├── .gitignore ├── main.py └── README.md /img/rpi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/rpi.jpg -------------------------------------------------------------------------------- /img/rx_rpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/rx_rpi.png -------------------------------------------------------------------------------- /img/receiver.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/receiver.jpg -------------------------------------------------------------------------------- /img/main_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/main_output.png -------------------------------------------------------------------------------- /img/rpi_config0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/rpi_config0.png -------------------------------------------------------------------------------- /img/rpi_config1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/rpi_config1.png -------------------------------------------------------------------------------- /img/rpi_config2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/rpi_config2.png -------------------------------------------------------------------------------- /img/rpi_config3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/rpi_config3.png -------------------------------------------------------------------------------- /img/transmitter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/transmitter.jpg -------------------------------------------------------------------------------- /schematic/rx_rpi.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/schematic/rx_rpi.fzz -------------------------------------------------------------------------------- /img/complete_build.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/complete_build.jpg -------------------------------------------------------------------------------- /img/flight_control_board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samfok/remote_receiver_tutorial/HEAD/img/flight_control_board.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # vim 104 | *.swp 105 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """Monitors the UART port on a Raspberry Pi 3 for Spektrum serial packets 2 | 3 | Assumes the packets follow the Remote Receiver format 4 | Forwards the packets on the TX pin of the serial port, so you can pass the 5 | packets on the the flight control board 6 | """ 7 | import serial 8 | import time 9 | import sys 10 | 11 | def align_serial(ser): 12 | """Aligns the serial stream with the incoming Spektrum packets 13 | 14 | Spektrum Remote Receivers (AKA Spektrum Satellite) communicate serially 15 | in 16 byte packets at 125000 bits per second (bps)(aka baud) but are 16 | compatible with the standard 115200bps rate. We don't control the output 17 | transmission timing of the Spektrum receiver unit and so might start 18 | reading from the serial port in the middle of a packet transmission. 19 | To align the reading from the serial port with the packet transmission, 20 | we use the timing between packets to detect the interval between packets 21 | 22 | Packets are communicated every 11ms. At 115200 bps, a bit is read in 23 | approximately 8.69us, so a 16 byte (128 bit) 24 | packet will take around 1.11ms to be communicated, leaving a gap of about 25 | 9.89ms between packets. We align our serial port reading with the protocol 26 | by detecting this gap between reads. 27 | 28 | Note that we do not use the packet header contents because 29 | 1) They are product dependent. Specifically, "internal" Spektrum 30 | receivers indicate the system protocol in the second byte of the header 31 | but "external" receivers do not. Further, different products are 32 | use different protocols and indicate this using the 33 | system protocol byte. 34 | 2) Other bytes in the packet may take on the same value as the header 35 | contents. No bit patterns of a byte are reserved, so any byte in the 36 | data payload of the packet could match the values of the header bytes. 37 | 38 | Inputs 39 | ------ 40 | ser: serial.Serial instance 41 | serial port to read from 42 | """ 43 | data = None 44 | # read in the first byte, might be a long delay in case the transmitter is 45 | # off when the program begins 46 | ser.read(1) 47 | dt = 0 48 | # wait for the next long delay between reads 49 | dt_threshold = 0.005 # pick some threshold between 8.69us and 9.89ms 50 | while dt < dt_threshold: 51 | start = time.time() 52 | ser.read() 53 | dt = time.time()-start 54 | # consume the rest of the packet 55 | ser.read(15) 56 | # should be aligned with protocol now 57 | 58 | MASK_CH_ID = 0b01111000 # 0x78 59 | SHIFT_CH_ID = 3 60 | MASK_SERVO_POS_HIGH = 0b00000111 # 0x07 61 | def parse_channel_data(data): 62 | """Parse a channel's 2 bytes of data in a remote receiver packet 63 | 64 | Inputs 65 | ------ 66 | data: 2 byte long string (currently only supporting Python 2) 67 | Bytes within the remote receiver packet representing a channel's data 68 | 69 | Outputs 70 | ------- 71 | channel_id, channel_data 72 | """ 73 | ch_id = (ord(data[0]) & MASK_CH_ID) >> SHIFT_CH_ID 74 | ch_data = ( 75 | ((ord(data[0]) & MASK_SERVO_POS_HIGH) << 8) | ord(data[1])) 76 | ch_data = 988 + (ch_data >> 1) 77 | return ch_id, ch_data 78 | 79 | print("Throttle Roll Pitch Yaw AUX1 AUX2") 80 | ser = serial.Serial( 81 | port="/dev/serial0", baudrate=115200, 82 | bytesize=serial.EIGHTBITS, 83 | parity=serial.PARITY_NONE, 84 | stopbits=serial.STOPBITS_ONE) 85 | N_CHAN = 13 86 | data = None 87 | servo_position = [0 for i in range(N_CHAN)] 88 | try: 89 | align_serial(ser) 90 | while True: 91 | data_buf = ser.read(16) 92 | data = data_buf[2:] 93 | for i in range(7): 94 | ch_id, s_pos = parse_channel_data(data[2*i:2*i+2]) 95 | servo_position[ch_id] = s_pos 96 | sys.stdout.write( 97 | " %4d %4d %4d %4d %4d %4d\r"%tuple( 98 | servo_position[:6])) 99 | sys.stdout.flush() 100 | ser.write(data_buf) 101 | except(KeyboardInterrupt, SystemExit): 102 | ser.close() 103 | except(Exception) as ex: 104 | print ex 105 | ser.close() 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remote_receiver_tutorial 2 | 3 | This tutorial demonstrates interfacing a radio transmitter and receiver with a 4 | Raspberry Pi over the Spektrum remote receiver (aka Spektrum Satellite) 5 | protocol. 6 | 7 | ## You will need 8 | * A radio transmitter: I used the Spektrum 9 | [DX6](http://spektrumrc.com/Products/Default.aspx?ProdId=SPMR6750) 10 | transmitter but others should work too 11 | 12 | ![](img/transmitter.jpg) 13 | 14 | 15 | * A radio receiver: I used a Spektrum 16 | [AR7700](http://spektrumrc.com/Products/Default.aspx?ProdID=SPMAR7700) 17 | and a Spektrum 18 | [DSMX remote receiver](http://spektrumrc.com/Products/Default.aspx?ProdID=SPM9645) 19 | (came with the AR7700), but you only need one that is compatible with 20 | your transmitter and has a Spektrum remote receiver protocol output. I assume 21 | that your transmitter and receiver have already been bound. Instructions 22 | for binding transmitters and receivers should be found with the manufacturer. 23 | 24 | ![](img/receiver.jpg) 25 | 26 | Example receivers capable of communicating over the remote receiver serial 27 | protocol: AR7700 (left) and DSMX remote receiver(right) 28 | 29 | * A Raspberry Pi: I used a Raspberry Pi 3 with Raspbian Stretch installed. 30 | It may actually be easier to interface with older Raspberry Pis and OS 31 | distributions because of how Raspberry Pi's now handle their UART ports. 32 | 33 | ![](img/rpi.jpg) 34 | 35 | * (optional) A flight control board: If you want to send rc signals to a flight 36 | control board capable of handling the remote receiver protocol. I used an 37 | Omnibus F3 Pro. Configuring flight control boards is beyond the scope of this 38 | tutorial. 39 | 40 | ![](img/flight_control_board.jpg) 41 | 42 | ## Hardware wiring 43 | * Connect the receiver's ground to the Raspberry Pi ground. 44 | * Connect the receiver's power to the appropriate Raspberry Pi power 45 | pin. For example, the AR7700, connect the Raspberry Pi's 5V output to the 46 | AR7700 5V rail, and for the Spektrum Remote Receiver unit, connect the 47 | Raspberry Pi's 3.3V output to the receiver's 3.3V line) 48 | * Connect the receiver's signal line to the Raspberry Pi RXD UART GPIO pin 49 | (GPIO 15 / pin 10 on the Raspberry Pi 3). Beware that the Pi's GPIO pins 50 | operate at 3.3V, so if your receiver outputs a 5V signal, you'll want a 51 | level-shifter to avoid ruining the Pi's GPIOs. 52 | 53 | ![](img/rx_rpi.png) 54 | 55 | Diagram of a remote receiver connected to the Raspberry Pi. Note whether your 56 | receiver uses 3.3V or 5V input power. 57 | 58 | * (optional) Connect the Raspberry Pi's TXD UART GPIO pin (GPIO 14 / pin 8 on 59 | the Raspberry Pi 3) to the flight control board if you would like to forward 60 | the data from the receiver to the Raspberry Pi to the flight control board. 61 | 62 | ![](img/complete_build.jpg) 63 | 64 | Picture of the receiver unit (foreground, right) and the optional flight 65 | control board (foreground, left) connected to the Raspberry Pi. 66 | 67 | ## Raspberry Pi configuration 68 | 69 | We need to setup the software environment and the serial port before running 70 | the code. 71 | 72 | ### Software requirements 73 | * Python 2.7 74 | * Python pip - Install with `sudo apt install python-pip` 75 | * pySerial - Install with `sudo pip install pyserial` 76 | * A clone of this tutorial repo 77 | 78 | ### Serial port setup 79 | The serial communication will use the UART pins on the Raspberry Pi 3 and have 80 | a rather complicated setup as documented 81 | [here](https://www.raspberrypi.org/documentation/configuration/uart.md) 82 | and nicely blogged about 83 | [here](https://spellfoundry.com/2016/05/29/configuring-gpio-serial-port-raspbian-jessie-including-pi-3/). 84 | There are two UART ports: a hardware UART port and a "mini" UART port which are 85 | accessed at `/dev/ttyAMA0` and `/dev/ttyS0`, respectively. 86 | 87 | There are a some defaults that confound our use of GPIOs 15 and 14 for serial 88 | communication with the receiver: 89 | * By default, GPIOs 15 and 14 are connected to the mini UART at `/dev/ttyS0` 90 | because the bluetooth module is using the hardware UART at `/dev/ttyAMA0`. 91 | Unfortunately, the mini UART is flakier than the hardware UART, so we'd like to 92 | connect GPIOs 15 and 14 to hardware UART at `/dev/ttyAMA0`. To do so, we need 93 | to either swap the port used by bluetooth or disable bluetooth. 94 | * To swap the UART port used by the bluetooth module, add 95 | `dtoverlay=pi3-miniuart-bt` to the Pi's `/boot/config.txt` 96 | * To disable the bluetooth module entirely, add 97 | `dtoverlay=pi3-disable-bt` to the Pi's `/boot/config.txt` (recommended) 98 | * By default, the Raspberry Pi uses the non-bluetooth serial port to provide 99 | a serial console. We need to disable the console to allow the port to be used 100 | for our serial communication with the receiver. To do so, run 101 | `sudo raspi-config` and then 102 | 103 | Select `Interfacing Options` 104 | 105 | ![](img/rpi_config0.png) 106 | 107 | Select `P6 Serial` 108 | 109 | ![](img/rpi_config1.png) 110 | 111 | Disable the login shell over serial 112 | 113 | ![](img/rpi_config2.png) 114 | 115 | Enable the serial port hardware 116 | 117 | ![](img/rpi_config3.png) 118 | 119 | ## Run the code 120 | Power on the transmitter and run `python main.py` from within the cloned repo 121 | on the Raspberry Pi. The code should output the values received from the 122 | transmitter. 123 | 124 | ![](img/main_output.png) 125 | 126 | Try toggling the transmitter power and seeing that the signals received stop 127 | and start with the transmitter power. Don't forget to peruse the code to 128 | see the gory details about the protocol implementation. 129 | Spektrum documents the remote receiver protocol 130 | [here](https://www.spektrumrc.com/ProdInfo/Files/Remote%20Receiver%20Interfacing%20Rev%20A.pdf) 131 | 132 | 133 | (optional) If you [connected](#hardware_wiring) the UART TXD (GPIO 14 / pin 14) 134 | to a flight control board set up to receive data according to the remote 135 | receiver protocol, you'll be able to also view the RC signals in, for example, 136 | Betaflight. 137 | 138 | ## Background 139 | I wanted to put a Raspberry Pi on a quadcopter and use it for some of the 140 | quadcopter's control, so I needed to find a way to connect the Pi to the radio 141 | receiver used to receive control signals from the transmitter on the ground. 142 | Honestly, it's a mess out there with radio receivers. There are a lot of 143 | receivers out there with all sorts of available output options. Figuring out 144 | which receiver and protocol to use could eat up a lot of time. 145 | 146 | I started with a receiver (the Spektrum 147 | [AR610](http://spektrumrc.com/Products/Default.aspx?ProdID=SPMAR610)) 148 | onhand that output pulse-width modulated (PWM) signals. 149 | I found that it is possible to read PWM signals using the Raspberry Pi's GPIO 150 | pins and interrupts even though the Pi does not run a real-time operating 151 | system because the PWM signals output by the receiver were slow 152 | enough that the timing of the OS didn't significantly affect the signal 153 | measurements. Still, PWM signals are product of the days of DC motors and 154 | servos and not great for interfacing with a Raspberry Pi: 155 | 1. Each channel requires its own wire and pin, so your build becomes 156 | a rat's nest quickly. More wires and pins = more opportunities for circuit 157 | shorts and opens. 158 | 2. You'll always wonder whether the OS is going to hiccup and skew a 159 | pulse-width measurement during flight and cause your build to crash... 160 | 161 | The first issue was the biggest issue in my mind since I had to maintain the 162 | quadcopter build (i.e. make sure all of the wires went to the right places and 163 | stayed there on a platform subject to crashes and lots of vibrations. oy.). 164 | To move on from PWM, I obtained an AR7700, which had a nice set of output 165 | options (pulse position modulation (PPM), remote receiver output (Remote RX), 166 | and bidirectional serial receiver link (SRXL)), 167 | each of which used a single wire to communicate all of the channel data. 168 | PPM posed the same kind of problem of measuring pulse timing as PWM and so was 169 | eliminated as a good option. Remote receiver was selected because it was a 170 | proper digital, serial protocol and used by the remote receiver units, which 171 | are much smaller than the AR7700 and so attractive for use on quadcopters. 172 | 173 | ## Questions and Comments 174 | Raise an issue or drop me a message. 175 | --------------------------------------------------------------------------------