Servo Controller Design

Contents

This article will assume that you already know how to use Quartus Prime development software for the Intel/Altera Family of FPGAs. If you need a refresher please refer to the previous article about designing for Intel/Altera programmable chips. I also assume that you are familiar with the basic syntax of the Verilog hardware description language. The hardware in this project is implemented with the Terasic DE-10Lite development board for the MAX-10 FPGA family, but the process is similar for any of the Intel/Altera FPGA devices.

What is a Servo?

A servo motor is a specialized type of electric motor that provides precise control of angular or linear position, velocity, and acceleration. It consists of three main components: the motor, the control circuit, and the feedback device. The motor generates motion, the control circuit manages the motor’s movement based on input signals, and the feedback device measures the motor’s position or movement to ensure accuracy and precision.

https://howtomechatronics.com/how-it-works/how-servo-motors-work-how-to-control-servos-using-arduino/

Servo motors are widely used in various applications, including robotics, automation, manufacturing, and aerospace, due to their high accuracy, speed, and torque capabilities. They are designed as a closed-loop feedback system, which allows them to maintain the desired position or speed even when external forces are acting upon them.

In this article, we will discuss building a servo motor controller on an FPGA board. We will examine the standard signals that are used to control a servo motor, and we will implement our own controller hardware.

Designing a Servo PWM Controller

The controller sends position commands to the servo using a Pulse-Width Modulation (PWM) signal. This signal typically has a period of around 20ms. The pulse width of the signal encodes position command information with a high and Low voltage value. Typically a hobby-grade servo uses a signal where a pulse width of 1.5ms indicates a neutral servo position, 1ms indicates a minimum travel position, and 2.0ms indicates a maximum travel position. Our controller will have three states assigned with combinations of buttons on the FPGA board corresponding to the maximum, minimum, and neutral servo travel positions.

Now that we have defined the specifications for our controller output lets examine a commercial servo control system and examine the signals to see how they compare.

Measuring a Servo PWM Reference Signal

I pulled the electronics out of a remote-controlled plane to use for this project. The first thing that we need to do is measure the signal that the radio receiver was sending to the servo. In place of the servo for the rudder channel, I placed an oscilloscope probe to measure the PWM signal from this Spektrum remote control receiver.

If we look at the screen capture from the oscilloscope, we can see the PWM signal coming from the controller to the servo. It has a period of 22.02ms with a duty cycle of 6.83%. This is the command for a centered servo.

If we push the stick on the controller all the way to the right we can see that the duty cycle has increased to 8.16%.

If we push the stick all the way to the left then we get a duty cycle of 5.50%.

By decreasing the horizontal time scale we can “zoom in” on the pulse and make some measurements.

We can use the cursor feature of the oscilloscope to measure the period between the rise and fall of the PWM signal. For the servo in the center position the pulse is around 1.5ms as denoted by the $\delta X$ measurement on the scope. This matches the 6.83% duty cycle for a 22.02ms total period.

If we measure the PWM period when the servo is at is maximum value we get around 1.8ms. This pulse duration also matches an 8.16% duty cycle for a 22.02 ms period.

Finally we measure the signal when the servo is at the minimum travel. We can see that the pulse width is 1.220ms which is very close to the 5.5% duty cycle for a 22.02ms PWM period.

Digital PWM Servo Control

It is relatively simple to implement a PWM controller in digital logic. We can generate a sawtooth wave by building a simple counter and then compare that wave to a setpoint to get our output signal. Anytime the sawtooth wave is below our setpoint, we output a high signal, and anytime it is above the setpoint, we output a low signal. A digital implementation of a sawtooth wave is a simple counter that counts from 0 to a predetermined maximum level and then resets and continues.

https://www.mathworks.com/help/sps/ref/pwmgenerator.html

The MAX-10 FPGA that we are using has a 50Mhz clock, which has a period of 20ns. Given that we want out PWM signal to have a period of 20ms we would require 20,000,000 clock cycles, therefore our counter must count up to 20 million. That seems like more work than we should need to do, not to mention that we must store that number somewhere.

If we look at our specs from above, we only have the duty cycle out to 2 decimal places. The precision of our measurement is only out to one-hundredth of a percent, so we don’t need our counter to have the precision to a twenty-millionth of a cycle. If we say that we only need precision to a 10th of a percent, now our counter only has to count up to 1,000, so we can store this as a 10-bit binary number. If we then split our 20ms PWM period into 1000, we get our desired clock period of 20 microseconds which corresponds to a clock frequency of 50hKz.

Then the only other thing that we need is an input that represents the desired duty cycle, which can be represented as another 10-bit number. By comparing the counter to the input, we can generate our desired PWM signal.

Generating a Slower Clock with a Phase-Locked Loop

We will use a Phase-Locked loop to slow the clock signal for our PWM counter. After Building a new project and opening up Qsys we can add a Phase-Locked Loop (PLL) to our design. A phase-locked loop is a device that can match a frequency of an electronic signal. We won’t get into the details as I am saving those for a later article, but all you need to know was that the PLL can ‘lock’ onto a repeating signal, therefore it can be used as a clock divider to output a signal that has a lower frequency than the input signal but without a time delay, aka “in phase”.

This system design will be simpler than the previous NIOS2 designs as we only have to add a single element. The PLLs in the MAX-10 Are actual hardware devices located on the periphery of the FPGA chip, so the design block that we will add to our system is simply an interface for those circuits.

Taken from the “Intel Max 10 Clocking and PLL User Guide”

In the IP catalog on the top-left of the screen search for PLL. Select the Avalon ALTPLL and click ‘Add’.

In the Configuration window that pops up, change the input clock frequency to 50Mhz. Then click on the ‘Output Clocks’ Tab.

Select the output clock frequency radio button and change the output frequency to 0.05 MHz (this is the same as 50 kHz.) then click ‘Finish’ until you exit the configuration wizard.

When we are back at our system we can see that we have Two elements, our PLL and our Clock. We need to connect the output from our clock to the input of our PLL, the clk_reset to our pll-reset, and then export the PLL output so that we can then connect it to our PWM controller.

We can then save and generate the system. One the system has been generated we need to add the .qip file to our project. This can be found in the synthesis folder of the generated Qsys system. We also need to add our design into the top-level verilog project file. The qsys folder also has instantiation templates that we can simply copy and use in our system.

We can add the PLL at the bottom of our top-level file with the following code.

Since we are adding another clock we need to declare a wire to be able to pass it into our PWM module. Therefore under the wire declarations we will add a new wire and call it PLL_50khz.

We can then connect our new wire to the input port of the PLL clock. The main clock source for the module is the MAX10_CLK1_50 and the default ARDUINO_RESET_N pin is used as the reset for our module.

PWM Servo Code

The main code for our module is written in Verilog The first part of our PWM servo controller that we’re going to look at is the module definition.

As we can see, we have a clock input signal, negative reset input signal, dutyCycle signal, and PWM output signal. When developing Verilog logic it is important that all input signals are wires and busses, while all output signals are registered.

The next line that we are going to look at is our 9-bit internal counter. This is not accessible from outside the module and its value is the height of the sawtooth wave that we are using to compare to a level, which is the dutyCycle input.

Next we can handle the reset logic.

In this code block, on the rising edge of every clock cycle the counter is reset to zero if the reset signal is pulled low.

The next part is the block that compares the level associated with the duty cycle to the current value of the counter. If the value is less, then the output signal is driven high, while if it is larger, then the output signal is pulled low. This is the main logic for generating our output signal. Yes, the heart and soul of this entire project is a simple comparison element. It’s just all the other stuff that we need to make this work that takes up most of the time and energy.

Finally, because the maximum value that we are looking for for our counter is less than the maximum value that can be stored in a 10-bit number (1024) we need to build an automatic reset for our counter. If the counter’s value is greater than 999, then we force a reset to zero. If the number were exactly a power of 2, we wouldn’t need to do this, as the number would automatically overflow to zero when we added a 1 to the maximum value.

The final end module line finishes off the module. All that’s left is to look at the test code.

PWM Testbench

Now that we have the specifications for our PWM controller we can design a testbench for it to determine if it is working properly once we write the actual module. A testbench is an HDL code file that runs a test of the hardware logic.

At the top of the testbench we need to set the timescale and precision for this simulation. I chose a 1us timescale with 1ns precision.

Once we set the timescale we can define the module. We need a register type for any values that we want to store, change, or input to our test module. In this case, that includes the clock and the values for the 1ms 1.5ms and 2ms pulse width. Finally we define a negative reset that can reset the clock.

Next we will instantiate 3 instances of our PWM controller, one for each different pulse width. Besides the different duty cycle inputs the port maps are all the same. With the port map, the port with the “.” is the port that is listed in the definition of the module under test and the expression in the parenthesis is the variable in the current module that will be mapped to that port. Each of the modules is connected to the same clock and reset, but they all have different duty cycles and output signals.

Next we will create a 50Khz clock. This is accomplished by switching the clock at every 10 microseconds (which is half of the desired period of 20us).

Next we create an initial block that prints the output of our PWM generator to the screen whenever a signal changes using the $monitor macro. The $monitor macro uses standard C-string formatting expressions to display values as the simulation progresses. Here we display the time, clock, and outputs of all 3 devices to be able to analyze later. The $Display macro prints the labels of our values at the start of the simulation run.

Finally we run the simulation for 45ms. First we set the reset and clock to high, and then we set or duty-cycle representations for each of the 3 devices.

I had actually created the servoPWM verilog files and testbenches on a different machine so I need to import them into models, but even if you haven’t you still need to bring them into Modelsim to verify their behavior. Modelsim is the Intel/Altera HDL development environment. You can get to it through Quartus Prime, or it can be run on its own. Open a new project in Modelsim (you can name it whatever you would like) and then right clink your project workspace and select “Add existing file” from the “Add to Project” menu. It will bring up a file import window.

Once you’ve imported the servoPWM and testbench files it is time to compile them. Right Click on your project workspace, then select “Compile” and then “Compile All”. If everything works, you should see two green check marks next to your files.

Switch to the library tab at the bottom of the page.

Open up the “work” folder, right-click on the testbench and select ‘simulate’. It will open up a new window that looks something like this.

Highlight all of your objects in the object window and drag them into the wave window.

At the top toolbar click on the icon that looks like this. It means run-all. If you get a prompt asking if you really want to finish click ‘No’ to keep the output on the screen.

Finally we need to zoom all the way out by finding the zoom full icon.

Our wave window should look like this.

We can see that we have three output PWM signals with a 1ms, 1.5ms, and 2ms pulse width. Additionally each of these signals have a period of 20ms. This looks exactly like what we expected it to look like.

Placing the Design on the FPGA

Now that we have tested our PWM generator we need to get it into our FPGA board design. We do that by selecting Project -> Add/Remove Files in Project from the top menu of the Quartus software. We can then navigate to the ServoPWM.v source file and add it. Once you’ve added it the file will appear at the top of the file list.

We can then connect our module to the output of the 50Khz PLL, the negative reset, our output and a constant value for the duty cycle. Our output pin is going to be the Arduino pin 0 on the DE-10Lite and we will use a constant duty cycle that represents a 1.5ms pulse.

Click ‘Compile Design’.

Once we verify that the design compiles we can flash our design to the board and see what it looks like. For a refresher on uploading the bitstream to the FPGA please refer to the previous tutorial on the NIOS 2 processor.

Now we can set up the oscilloscope to look at the outputs. To do this we connect an oscilloscope probe to the Arduino_IO0 pin. This is the pin that we specified in the top-level file.

Taken from the DE10-Lite User Manual

When we examine the signal we can see the following output on the oscilloscope.

We can see that we have a clear signal with repeating pulses. Our signal has a period of 20.02ms which gives us a frequency of around 50kHz. This signal also has a duty cycle of 7.59%

When we zoom in closer we can measure the duration of the pulse width. It is around 1.5ms which is exactly what we expect.

When we hook this up to the servo signal wire we can see that the servo remains in its centered position even when we apply a force to disturb it. We know that our signal is commanding the servo to it’s center position therefore we expect it to do so up until the maximum torque limits of the actuator.

It also can reset to the center if we remove the command signal, move the servo, and then re-connect the signal wire.

Let’s modify our design so that we can change the duty cycle between the three setpoints that we tested. By adding an additional 10-bit wire we can select from the 3 setpoints based on our switch configuration. We can add a register to store a representation of the duty cycle for our servo. Then we switch that value based on the current position of the pushbutton keys on the board by reading the keys as a 2-bit binary number.

Once we can load the design onto the oscilloscope we can see the effects of our change. With no buttons pressed the value doesn’t change because the buttons are active on a low signal. With both buttons pressed we get a 1ms pulse.

With the first button pressed we see that there is a 1.5ms output pulse.

And with the second button pressed we get a 2ms output pulse

By toggling these buttons we can change the direction of the servo.

Conclusion

Congratulations! You are now on your way to designing digital electro-mechanical systems. The single servo-motor is a good example of the intricacies of interfacing between different components (which is the bulk of what engineering is). It also shows how we can use a digital hardware design on an FPGA to control a different piece of hardware using a very basic signal protocol. We determined the specifications of an unknown protocol by measuring it with an oscilloscope. Then we created a hardware design to implement this protocol, combining custom-designed logic and on-chip hardware resources into a combined system. Finally, we used our system to take in inputs from off-chip buttons and translate that into a signal to control an electronic servo.

The HDL code is available on Github.