15 Aug

PYNQ-copter’s Maiden voyage

Category:REU at UCSDTag: , , , :
This hexrotor was designed using the Xilinx PYNQ board. The system currently has an open-loop controller, but the other members of my group are working toward integrating an Inertial Measurement Unit (IMU) and barometer for a closed-loop controller.
What sets this apart from other small unmanned aerial vehicles (UAVs) is that all computation is handled within the Programmable Logic (PL) fabric of the ZYNQ Z7020. Unlike similar Field-Programmable Gate Array (FPGA)-based UAVs, this logic is hard. This means that rather than using microcontrollers implemented onboard the FPGA like other UAVs, the functions are implemented in C++ using Vivado HLS and synthesized to Verilog.
 Within the next few weeks, my team hopes to complete integration of sensors into the controller to allow a closed-loop PID controller to be used for control of the UAV’s attitude and altitude.


Disclaimer: this is a technical section. The goal of this section is to overview the implementation details of the current system, prior to the addition of sensors.

To begin, this project had a few goals other than simply getting a hexrotor off of the ground. The first goal was that we would only use C++ to design the system. No Verilog nor Tcl was written beyond simple additions to the PYNQ board by Dustin Richmond to fix issues with the PYNQ API. The second goal was to design the system in a way that others may be able to build off of it. This means that one of our goals was to allow reconfiguration. Both of these goals were made easy by the Vivado Design Suite which allowed us to write C++ which was synthesized to Verilog and VHDL then RTL to be used onboard and also by the PYNQ libraries which allowed us to interact with the AXI interface of individual IPs. This would probably be a good place to discuss the implementation of each individual IP.

System Architecture

The PYNQ-copter at a physical level is designed using four main components, the PYNQ board by Xilinx, the DJI Flamewheel F550, sensors, and an FrSky Telemetry receiver. The PYNQ board was designed to act as the controller for the system. If you are familiar with UAVs, the PYNQ is substituting for the function of an Ardupilot or N3.

In the above demonstration, we had not yet been added the sensors to the system and we flew with an open loop control system. In this system, the RC receiver outputs 6 channels of PWM signals which are deciphered by the PYNQ board. The first four channels are translated to roll, pitch, thrust, and yaw commands which are passed through a motor mixer to decide what each motors’ power should be. That power is then passed through a PWM generator to encode their respective powers as PWM signals. The 5th channel acts as a kill switch. This means that if the switch associated with channel 5 is flipped, the motors immediately stop. The 6th channel is currently reserved for later use.


The first IP in the flow of data is the RC_Receiver IP. This IP takes data from the Arduino pins on the PYNQ board. This is accomplished by taking all of the inputs, slicing the last six bits off,  passing those six through a synchronizer, then slicing the last six bits on the output of the synchronizer and lacing them as ap_none inputs or inputs without validation into the RC_Receiver. The part of this which may stick out is that of the synchronizer. A problem when first developing the RC_Receiver is that the FPGA runs at a different clock rate than the microcontroller on the physical receiver. This leads to reads from the IP core when the edge of the pulse from the physical receiver was still unresolved. This component is a simple line of registers which resolves the input by the end of the line to a 1 or 0 instead of a random value. The IP core treats the 6 channels as a 6-bit arbitrary precision unsigned integer.

Inside the IP core, an accumulator is tracked for each channel so that the length of the pulse may be found in ticks of the FPGAs clock. This method makes a few assumptions that may be allowed due to the operation of the physical RC receiver.  The first is that, at max, only one channel’s value will be updated at any given time. This will allow us to use a master AXI port as the output and only update a single address each cycle. This reduces the initialization interval to 1 as only 1 m_axi write may be done per cycle. Those ticks are then passed as unsigned integers to the next IP in the flow, the normalizer.


The output of the RC_Receiver is gibberish without a comparable benchmark. This node’s job is to compare the gibberish with known values for the maximum and minimum tick counts and generate a value in the range [0,1). Those values may be found by throttling the RC and looking at the values in the registers of this core using the Jupyter Notebooks interface. The minimum and maximum values from those registers may be used to set the min and max for normalization. In this method, we make the same assumption as above and we also can use the clock rate to limit the bytes in the fixed point to 16 bits after the decimal without a loss of precision. Six 16 bit fixed point values are passed from this core to the mixer.


The next step in the data flow is to mix the roll, pitch, thrust, and yaw signals to each individual motors’ power. This step may be abstracted as multiplying a 6 by 3 matrix of mixing variables by a 3 by 1 matrix of the roll, pitch, and yaw commands and then scaling by the thrust factor to generate a 6 by 1 matrix of each motor’s power. Those powers are passed as 16 bit fixed point values to the PWM generator.

PWM Generation and Motor Driving

The values received from the mixer are transformed into PWM signals for each motor. The inputs are the values from the mixer as well as 3 values from its other s_axilite ports. These other three values are calibrated unsigned integers which represent the minimum duty cycle, the maximum, and the period length. The values are found through experimentation and used to match the specification of the maximum frequency that the Electronic Speed Controllers (ESCs) allow. For us, this was 1.12ms, 1.92ms, and 2.5ms respectively. The algorithm of this method is that is stores the channels locally and updates at the end of each cycle. The basic algorithm is to reset when the accumulator is equal to the period, set all channels high if less than the min duty cycle, and set all channels low if above max duty cycle. If between min and max, for each channel, if the accumulator is greater than the channels’ power converted to the min-max range, set the channel low. The channel may not be set high again until after the period. This prevents repeated rising edges which can lead to unknown effects in high-frequency systems and unexpected thrust in low-frequency systems.

Looking Forward

By the end of this week, we hope to integrate the sensors into the system to allow for closed-loop control. We also hope to have the parameters tuned through single axis tuning so that it will be safe to fly in rate mode by the end of next week. If all goes well, we may try to redefine the errors which we place into the PID controller to gain attitude control. Until my team finishes the integration of the sensors, I will personally be writing up documentation for everything I have done as well as writing manuals to help students and/or teachers use this system to teach synthesis. Over the remainder of the program, I hope to write an educationally focused paper, a technical document, heavy code documentation, and a user manual. These will allow others to pick up the system for further research and allow educators to use this to teach.