It is true that we can control a motor with a simple 8 bit microcontroller, outputting a simple Pulse Width Modulation waveform. However, when you want to do precision or advanced motor control nothing beats the determinism, and real time response of an FPGA. The flexible nature of the interfaces also enables multiple motors to be controlled from a single device providing a more integrated solution.
Over a couple of projects I want to look at motor control from FPGA including the simplest PWM drives to using sensors to feedback and accurately control the speed of the motor using PID control etc.
To get started with we are going to learn a little about the theory of motor control and create a simple example of this. We all know that we can drive a DC motor and control its speed with a PWM signal. However, driving it efficiently and with precision takes more understanding of motor control theory.
MotorsBelieve it or not one of the classes I enjoyed the most at university was the module on machines. In that module we looked at both AC and DC Motors, learning about the theory and practical use cases. There are several types of motor AC which operate from AC supplies and can be grouped as synchronous or induction. Typically AC motors are used for Pumps and Compressors for example.
DC motors are split into two groups Brushed and Brushless. Of the two types brushed are the easiest to drive as they require only a single supply. In a brushed DC motor the brushes supply the current to the commutator, which has rotor and coils attached. The current induces a electric field in the coils which are repealed by the outer magnets (stator). To ensure rotation the commutator is designed such that the flow of current reverses to ensure continual rotation.
The second type of DC motor is the brushless, these are a little more complex to drive as they do not have a commutator. Instead the magnets are mounted on the rotor and the coils are wrapped around the stator as such the current to the coils is controlled and sequenced externally.
The simplest of the two to control is brushed DC, which in this example is what we are going to look at.
PWM DriveThe theory of driving motors with a PWM is that we can very the average voltage the motor sees and hence control it speed. At 100% duty cycle of the PWM signal the motor sees it full voltage and runs at full speed. Providing a 10% duty cycle should see the motor operate at 10% of its full speed.
However to run the motor efficiently we need to correctly determine the PWM period. DC motors have a series inductance and series resistance, this means the motor will act as a low pass filter. With a cut of frequency of
Where the time constant is given by L/R - We can get these from the motor data sheet.
To ensure a steady speed we need to therefore select a PWM frequency which, above the cut of frequency of the motor to ensure the DC component is observed.
We therefore want to select a frequency which is at least 5 times the cut of frequency.
VivadoTo get started with this project we are first going to create a hardware design which targets the Basys 3 board, this has a small Artix 35 FPGA fitted. We are going to drive the motor from a Pmod H bridge 3 which enables the use of higher voltages which are needed for the motor.
To get started create a new project
Name the projects
Select RTL project but do not specify sources
Select the Basys 3 as the target board
When the project is created, create a new block diagram
From the boards tab pull across the system clock onto the block diagram
Do the same for the USB UART also
From the IP library add a MicroBlaze Processor
Run the block automation, select the local memory size as 32KB and uncheck the interrupt controller
This should create the MicroBlaze sub system
Add in a AXI Timer
Run the connection automation
Open the clocking wizard and de select the reset input
Run the validation you should not see any errors or critical warnings.
Add in a GPIO
Re customise the GPIO to be 1 bit wide and output only
Select the GPIO output and the AXI Timer PWM and make them external
The completed should look like below.
Create a HDL wrapper and run the synthesis
Once synthesis is finished, we can open the synthesis view and assign IO to the GPIO and timer outputs - for the GPIO the Pin is J1 for the PWM it is L2
Save the constraints to a file
Build the bitstream and export the platform
Select include bitstream
Leave the defaults unchanged
Click Finish
Open Vitis and select the workspace you want to operate in. Create a new application project and select the XSA which was just exported.
Enter a project name
Select stand alone
Create a new hello world application
The application software is very simple we are going to configure the AXI timer for the PWM period we require, along with the desired duty cycle.
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xtmrctr.h"
#define TMRCTR_DEVICE_ID XPAR_TMRCTR_0_DEVICE_ID
#define PWM_PERIOD 1000000 /* PWM period in (500 ms) */
#define TMRCTR_0 0 /* Timer 0 ID */
#define TMRCTR_1 1 /* Timer 1 ID */
#define CYCLE_PER_DUTYCYCLE 10 /* Clock cycles per duty cycle */
#define MAX_DUTYCYCLE 100 /* Max duty cycle */
#define DUTYCYCLE_DIVISOR 2 /* Duty cycle Divisor */
XTmrCtr TimerCounterInst;
void display_menu()
{
//Clear the screen
xil_printf("\033[2J");
//Display the main menu
xil_printf("*******************************************\n\r");
xil_printf("**** www.adiuvoengineering.com ****\n\r");
xil_printf("**** Motor Control Example ****\n\r");
xil_printf("*******************************************\n\r");
xil_printf("\n\r");
xil_printf(" MM10 Motor Control \n\r");
xil_printf("------------------------------------------\n");
xil_printf("\n\r");
xil_printf("Select a Speed:\n\r");
xil_printf(" (1) - Stop\n\r");
xil_printf(" (2) - 25 % \n\r");
xil_printf(" (3) - 33 % \n\r");
xil_printf(" (4) - 50 % \n\r");
xil_printf(" (5) - 66 % \n\r");
xil_printf(" (6) - 75 % \n\r");
xil_printf(" (7) - 100 % \n\r");
xil_printf("\n");
}
void set_pwm(u32 cycle)
{
u32 HighTime;
XTmrCtr_PwmDisable(&TimerCounterInst);
HighTime = PWM_PERIOD * (( float) cycle / 100.0 );
XTmrCtr_PwmConfigure(&TimerCounterInst, PWM_PERIOD, HighTime);
XTmrCtr_PwmEnable(&TimerCounterInst);
}
int main()
{
u8 Div;
u32 Period;
u32 HighTime;
char key_input;
u8 DutyCycle;
init_platform();
print("Hello World\n\r");
print("Successfully ran Hello World application");
XTmrCtr_Initialize(&TimerCounterInst, TMRCTR_DEVICE_ID);
Div = DUTYCYCLE_DIVISOR;
XTmrCtr_PwmDisable(&TimerCounterInst);
Period = PWM_PERIOD;
HighTime = PWM_PERIOD / Div--;
XTmrCtr_PwmConfigure(&TimerCounterInst, Period, HighTime);
XTmrCtr_PwmEnable(&TimerCounterInst);
while(1){
display_menu();
read(1, (char*)&key_input, 1);
xil_printf("Echo %c\n\r",key_input);
switch (key_input) {
case '1': //stop
XTmrCtr_PwmDisable(&TimerCounterInst);
break;
case '2': //25%
xil_printf("25%\n\r");
DutyCycle = 25;
set_pwm(DutyCycle);
break;
case '3': //33%
DutyCycle = 33;
set_pwm(DutyCycle);
break;
case '4': //50%
DutyCycle = 50;
set_pwm(DutyCycle);
break;
case '5': //66%
DutyCycle = 66;
set_pwm(DutyCycle);
break;
case '6': //75%
DutyCycle = 75;
set_pwm(DutyCycle);
break;
case '7': //100%
DutyCycle = 100;
set_pwm(DutyCycle);
break;
}
}
cleanup_platform();
return 0;
}
Of course for complex speed control applications the motor I selected contains two hall effect sensors.
The direction of rotation can be determined by the output from one hall effect sensor being in front of the other.
Rotation Clockwise
Rotation Anti Clockwise
We can use the frequency of pulses to determine the speed of the motor, we will look into this a lot more detail in another project. But you can see in the plots above using a PMOD GPIO to power the Hall effect sensors and scoping the outputs the differences in rotation direction.
Wrap UPMotor control is a great application for FPGAs and we will be looking in more depth as the
Comments