Brushless DC motors are a popular choice for many motor-based applications due to electrical commutation being their control method. A brushless DC motor driven by electrical commutation can be up to 30% more efficient than a conventional brushed DC motor driven by a DC or PWM signal. BLDCs also have a longer lifespan than brushed DC motors since they don't have the same brushed components that wear down over time and cause poor contact.
In addition to having a longer lifespan, BLDCs are also popular for consumer devices due to them being able to achieve higher speeds/acceleration and being quieter both electrically and mechanically (sound).
The only drawbacks of BLDCs compared to their brushed DC motor counterparts is they are more expensive and are more complex to control. The required electrical commutation for a BLDC is performed externally of itself, involving the of switching current in the motor phases to generate motion. This is what the brushes do internally in brushed DC motors.
Without any of these types of internal components, BLDCs rely on external analog circuitry along with some form of digital controller such as a CPU, microcontroller, or FPGA. This is where development platforms such as the Kria KD240 Drives Starter Kit comes in.
The Kria KD240 Drives Starter Kit is comprised of an AMD Kria™ K24 SOM to do the heavy lifting in terms of processing and digital signal processing (DSP), and the KD240 carrier board to provide the aforementioned analog circuitry to drive BLDCs.
Paired with one of the motor accessory kits that AMD also offers, this project tutorial is the first in a series going over the basics of driving a BLDC with a SOM.
I'm using the AMD Kria™ KD240 Motor Accessory Pack that is currently available which consists of a BLWR11 Brushless DC Motor from Anaheim Automation that is also outfitted with one of their ENC-A5SI single-ended encoders with index channel.
BLDC Motor Drive CircuitryAs previously mentioned, brushed DC motors rely on their internal components to switch the current in the motor phases to generate motion. This means all a user has to do is simply apply a DC signal to the positive and negative terminals to get the motor to turn. Switching polarity of the input DC signal swaps the direction the motor is turning, and using a PWM signal with varying duty cycle can vary the speed the motor is turning.
H-bridges are used to drive brushed DC motors in applications where the motor's direction and speed need to be changed, and the user doesn't have to do things physically like swap the connectors on the motor's input to reverse the polarity and therefore motor direction.
An H-bridge was the circuitry on the HB3 Pmod board I used with my AMD Kria™ KR260 Robotics Starter Kit to drive the motors for my Lego robot in my previous project posts.
In contrast, a three-phase inverter circuit is used to change which phase of a BLDC is driven by a PWM or DC signal (logic "1"), connected to the return of the source (logic "0"), and left undriven at any given moment in time in order to create the appropriate magnetic fields within the BLDC to keep the rotor spinning.
Looking at the schematic for the KD240 carrier board (download here), sheet 19 shows the 3-phase inverter circuit used to drive a BLDC.
The tricky thing here is that the position of the permanent magnets in the rotor need to be known because if the appropriate signal (low: "0"/"1", undriven/high Z: "0"/"0", or PWM) isn't applied to the corresponding winding/coil in the stator, the rotor will never move. So now, knowing the position of the rotor is a requirement at all times versus just a desire based on application.
KD240 Motor Drive MethodsThere are a few different methods for determining and keeping track of the position of the rotor in a BLDC. The first differentiation amongst these drive methods is whether they use sensored versus sensorless positional feedback.
Sensored positional feedback BLDC drive methods involve using physical sensors connected to or embedded within the motor itself. The most common (and easiest to use) method is to have Hall effect sensors that are embedded within the windings of the BLDC motor such that they are at 120˚ angles electrically from each other (which equates to 120˚ out of phase with each other on the unit circle).
How this translates to physical positioning within the BLDC depends on how many poles it has (the more poles, the closer the three Hall effect sensors are physically).
The Hall effect sensors read out logic level "1" for one pole type (typically North) and logic level "0" for the opposite pole type (typically South) of a BLDC's rotor. And being at the 120˚ electrical separation, it creates a 6-step truth table for one electrical revolution of the BLDC's rotor - which again, depending on the number of poles the BLDC has, one electrical revolution may only be a part of one full mechanical revolution.
The 6 Hall effect sensor codes (binary 1 - binary 6), then translate to the aforementioned appropriate signals to apply to the tree inputs of the BLDC when that code is read out from the three sensors at any given moment. In the diagram below, "High" is when the PWM signal would be applied to that phase, "Low" is logic level "0", and "Float" is undriven aka high impedance Z.
Sensorless positional feedback BLDC drive methods by contrast, measure the current draw of each phase's windings to deduce the back EMF created by the pole of the rotor passing it.
The same 6-step electrical revolution truth table is created as with the Hall effect sensor, but the tricky part here is at start up where the position of the rotor is not known since it's not moving to generate the back EMF in the stator's windings.
So the motor just has to be started (aka - just start applying the 6-step truth table of PWM/Low/High Z signals to the phases of the BLDC) until enough back EMF is generated to determine the position of the rotor and therefore the appropriate code word to apply to the phases.
While there is sensored versus sensorless positional feedback, there are three different techniques for the drive signal input into the BLDC's phases: trapezoidal, sinusoidal and field oriented control (FOC).
The PWM/Low/High Z signal scheme I've explained above (and will be using in this project write-up) is an example of a trapezoidal control scheme. The PWM could also be replaced with a straight DC current as well. The cons of trapezoidal control of a BLDC are that it creates quite a bit of electrical and acoustic noise given that it's just a DC switching power supply at its core concept. It also causes the windings within the BLDC to heat up due to inefficiencies.
Sinusoidal control involves implementing a saddle-shaped sinusoid to each phase based on the rotor's position to create a sine-wave differential voltage between two terminals, phase-shifted by 120˚ for commutation. This method is very complex with implement and takes a lot of tuning:
FOC or field-oriented control is a form of vector control where the current position of the rotor and magnetic fields produced by the coils of the stator are measured and then translated into vectors so that the driver can always drive the phases of the BLDC such that an orthogonal magnetic field is created by the coils of the stator compared to the position of the permanent magnets in the rotor.
FOC is a very DSP-intensive method to apply and also requires tuning. AMD provides an out-of-the-box FOC application for the Kria KD240 kit, so while I won't be developing my own in this project series I will be showing the progression of how to use the Kria KD240 kit to get started with and characterize a BLDC in the progression of building up to the development of an FOC application.
Connecting Hall Sensors to the KD240Since I'm starting out with the simplest method for driving a BLDC (trapezoidal control using sensored positional determination with Hall sensors), the first step is to hook everything up. The three phases of the motor already have a connector on them in the KD240 Motor Accessory Pack that connects to J32 on the board.
However, the FOC application from AMD relies on back EMF and an encoder to for positional determination of the Anaheim BLDC. So while the Anaheim BLDC has Hall sensors built into it, they are not connected by default on the KD240 carrier board.
So I started by stripping the insulation back about a half inch on each of the 5 leads remain leads on the motor for the hall sensors (power, ground, and the three sensor outputs), then tinned ends of each (stranded 30 AWG) so I could use them with a breadboard for now.
Using the part number printed on the side of the motor, I pulled up its datasheet from Anaheim's website to get the pinout of the hall sensors (said datasheet is attached below as well):
It turns out that the torque sensor power supply on J45 connector is perfect to use for the hall sensor power supply since it just so happens to match Anaheim motors' hall sensor supply requirements at 4.5V. So I simply tied the yellow and white Hall Supply and Ground wires directly to the 4.5V supply and ground connections of the J45 screw terminal respectively:
The Pmod connector on the KD240 carrier board is really the only general purpose I/O available on the board. But it is perfect in that it also supplies a 3.3V rail to use as the pull up for the open collector hall sensor outputs.
The package pins of most AMD FPGAs have a 2mA maximum rating, thus to ensure the hall sensor lines would draw well below that I used 10kΩ pull up resistors to limit the current to 330µA on each (3.3V/10kΩ = 330µA).
With the motor all hooked up, I finished the hardware setup by connecting a microUSB cable to the JTAG/UART connector (J4), an ethernet cable from my network's router to PS GEM0 (J2), the main 12V power to the barrel jack (J12), and 24V motor power supply to J39 using the pigtail cable.
BLDC Controller HDL LogicAs mentioned in the section above, the electrical commutation used for driving a BLDC is also referred to as "6-Step Commutation" since you have a truth table of six possible states to drive the coils/phases of the stator based on the current position of the rotor.
When using Hall effect sensors, simply take the three-bit "code" they create to apply the corresponding values to the three phases of the BLDC. This basically just creates a truth table in the logic that when stepped through in one sequence causes the motor to rotate clockwise, and then when stepped through in the opposite sequence the motor rotates counterclockwise.
This is really straightforward to implement in HDL. First, I read in the hall sensor values to create 3-bit code by mapping them straight to variables. They could be registered and then kept synchronous with the FPGA system reset, but I didn't see any added value in that for this use case.
wire [2:0] hall_code;
assign hall_code[2:2] = hallA;
assign hall_code[1:1] = hallB;
assign hall_code[0:0] = hallC;
Then I use the 3-bit code word created by the Hall sensor input in a case statement to drive the 3 phase outputs accordingly with the actively phase being driven by a PWM signal respective to the desired speed of the BLDC (the sequence below is for counterclockwise rotation - swap the "0" and PWM phases for clockwise rotation):
always @ (posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
phaseA <= 1'bz;
phaseB <= 1'bz;
phaseC <= 1'bz;
end
else begin
case (hall_code[2:0])
1 : begin
phaseA <= 1'bz;
phaseB <= 1'b0;
phaseC <= motor_pwm; //1'b1;
end
2 : begin
phaseA <= 1'b0;
phaseB <= motor_pwm; //1'b1;
phaseC <= 1'bz;
end
3 : begin
phaseA <= 1'b0;
phaseB <= 1'bz;
phaseC <= motor_pwm; //1'b1;
end
4 : begin
phaseA <= motor_pwm; //1'b1;
phaseB <= 1'bz;
phaseC <= 1'b0;
end
5 : begin
phaseA <= motor_pwm; //1'b1;
phaseB <= 1'b0;
phaseC <= 1'bz;
end
6 : begin
phaseA <= 1'bz;
phaseB <= motor_pwm; //1'b1;
phaseC <= 1'b0;
end
default : begin
phaseA <= 1'bz;
phaseB <= 1'bz;
phaseC <= 1'bz;
end
endcase
end
end
I'm applying a PWM signal versus straight DC signal to active phase in the phase code in order to control the speed of the motor.
There are many different ways to generate a PWN signal in HDL. I just threw in the following process with a constant that I can change later when I make the PWM frequency (and therefore speed of the BLDC) variable:
always @ (posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
motor_speed_interval <= 22'd0;
motor_pwm <= 1'b0;
end
else begin
if (motor_speed_interval == speed_2000rpm) begin
motor_speed_interval <= 22'd0;
motor_pwm <= ~motor_pwm;
end
else begin
motor_speed_interval <= motor_speed_interval + 1;
end
end
end
I decided to start with RPM values on the lower end of the Anaheim motor's maximum of 10, 000 RPM just to err on the side of caution.
Adding BLDC Logic to Hardware Design in VivadoI am using AMD Vivado™ Design Suite/AMD Vitis™ Unified Software Platform version 2024.1 in this project write-up, but I have also verified this design/process in 2023.2 (I am developing on an Ubuntu 22.04 host PC - see my installation tutorial here).
Launch Vivado Design Suite and create a new project with the desired name/directory location, but DO NOT select the option to make the project an extensible Vitis Platform.
Since I want to be able to control the motor_en
pin with AXI GPIO that I step through C code of a standalone/no-OS application to toggle it high/low via the debugger in Vitis, the platform with the hooks for an accelerated kernel can't be present. Standalone/no-OS applications can't be debugged in the Vitis IDE.
Target the Kria KD240 Drives Starter Kit under the boards tab, and be sure to specify the companion board connections for it:
As usual, the first thing in a new AMD Zynq™ SoC-based Vivado project is to create a block design to instantiate the Zynq Processing System IP.
Search for "Zynq" in the IP catalog, add it to the block design, and run the sequent block automation that appears in order to apply the appropriate configuration for the KD240 board.
To add the custom HDL shown in the previous section for BLDC control, select Add Sources from the Flow Navigator window and walk through the steps for Add or create design sources to create the HDL source file. The full Verilog code can be copy+pasted into it from my source code attached below.
Add an AXI GPIO block configured with one channel of a single output bit and run the corresponding connection automation for it (but only for its AXI interface). This is connected to the motor_en
pin on the KD240 board for connecting/disconnecting the 24V power supply to the motor driver circuit.
Manually connect the output pin of the AXI GPIO to a port labeled "motor_en". Expand the interface, right-click on the pin, and select "Make External".
At this point, there is still the option to run connection automation. This is the final bit of configuration for connecting the Zynq PS to the AXI crossbar. Run this connection automation before proceeding.
To add the HDL module from a user source file to the block design, right-click anywhere in free space in the block design and select "Add Module". Select the motor controller module for the Verilog file added in the previous step.
Connect the clock and reset lines of the motor controller module to the same clock/reset sources as the AXI GPIO:
Then connect the hall sensor inputs and phase outputs to ports using the same "Make External" option:
I also added an extra Processor System Reset and AXI Interrupt controller for later when I do convert this project to an extensible Vitis platform to develop accelerated applications later. Once the block design is complete, validate the block design and save it.
Now that the block design is complete, it needs to be instantiated in the via an HDL wrapper source file.
Right-click on the block design file in the Sources window and select the option to Create HDL Wrapper... then select the option to let Vivado manage the file.
The next step is to add the constraints file to route signals such as the Hall sensor outputs and motor drivers to the appropriate FPGA package pins.
Again, select Add Sources from the Flow Navigator window and follow the steps for Add or create constraints to create a new constraints file.
Copy+paste the constraints from my source files attached below. I used the KD240 schematic with KD240 master constraints file to create this pinout.
Aside from the pinout, I also specified PULLNONE on phaseA/B/C lines (the motor drivers) so that it will actually be floating when the logic "turns off" a phase of the BLDC by setting it to high-impedance Z in the HDL logic.
With the design complete run synthesis, implementation, and generate a bitstream. Once a bitstream has been generated, export the hardware in the AMD compression XSA format to pull the design into Vitis Unified Software Platform and develop the software for it.
Select File > Export > Export Hardware...
Stepping through the export wizard screens, be sure to select the option to include the bitstream in the exported hardware.
The hardware can be exported to any desired directory location, but as I have mentioned in the past I like to put it in the top level directory of the corresponding Vivado Design Suite project it was created in so I can keep track of it.
Test Software for BLDC Logic in VitisWith the hardware design complete and exported, launch AMD Vitis™ Unified Software Platform and create a new workspace. But in the case of Vitis 2023.2 and beyond (so 2024.1 like I'm using here) there is no option to "create a new workspace" within the IDE. So you have to create an empty directory ahead of time to open into from the Vitis IDE instead.
Again, to keep everything organized I personally like to create a directory titled "vitis_workspace" in the top level of the corresponding Vivado project the hardware design was created in.
Upon opening Vitis, select Open Workspace and navigate to the directory just created:
Once a new workspace has been created the first step is to create a platform component targeting exported XSA file from Vivado.
Select Create Platform Component from the top left and walk through the screens to create a platform for the standalone domain using the exported XSA file from Vivado.
Once the platform component is generated, build the platform component by single left-clicking Build in the Flow window in the lower left:
With the platform component complete (and therefore hardware imported), create an application component from hello world example in library.
First go back to the Welcome tab and select Examples to open the application template library.
Then select Hello World from the application examples and select the option to Create Application Component From Template.
Walk through the setup windows to give the application the desired name and target it to the platform component created in the previous steps.
Once the application component has been generated, open the helloworld.c
source file (located under Sources > src) and replace its contents with the following to control the motor_en GPIO pin:
#include <stdio.h>
#include <xgpio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
XGpio Motor_Ena_Gpio;
int main()
{
int Status;
init_platform();
print("Hello World\n\r");
// initialize axi gpio connected to motor_en
Status = XGpio_Initialize(&Motor_Ena_Gpio, XPAR_MOTOR_ENA_BASEADDR);
if (Status != XST_SUCCESS) {
xil_printf("Gpio Initialization Failed\r\n");
return XST_FAILURE;
}
// set the gpio pin as an output
XGpio_SetDataDirection(&Motor_Ena_Gpio, 1, 0x00);
XGpio_DiscreteWrite(&Motor_Ena_Gpio, 1, 0x01);
print("Motor ON.\n\r");
XGpio_DiscreteClear(&Motor_Ena_Gpio, 1, 0x01);
print("Motor OFF.\n\r");
cleanup_platform();
return 0;
}
Build the application component by again single left-clicking Build in the Flow window in the lower left.
Before launching a debug run of the Hello World application in Vitis, add a jumper to J23 on the KD240 carrier board so that the Kria K24 SOM boots into JTAG mode:
Then connect the main 12V/3A power supply for the KD240 carrier board to J12 and the 24V/1.5A auxiliary power supply for the BLDC motor to J39:
Launch a debug run of the hello world application by single left-clicking Debug in the Flow window in the lower left.
This will cause Vitis to switch over to the debugger and set an automatic breakpoint at the first line within the target application's main function:
Step through the code to get to the line to set the GPIO for the motor_en signal and turn on the BLDC. Once the motor_en pin is set high (logic level "1") the motor will turn on.
The aforementioned noise will be apparent, especially at higher RPMs (remember, the motor in the AMD motor kit has a max rating of 10, 000 RPM so do not exceed that). Do not leave the motor running for an extended period of time while using this design either, as it does cause the motor to heat up over time due to the inefficiencies of the trapezoidal control method. But now that we have the basic theory down for driving a BLDC motor, we can move on to improving upon these inefficiencies.
AMD sponsored this project. The opinions expressed in this project are those of Whitney Knitter. All opinions are provided by Whitney Knitter and have not been independently verified by AMD. Performance benefits are impacted by a variety of variables. Results herein are specific to Whitney Knitter and may not be typical. AMD, the AMD Arrow logo, Kria, Vitis, Vivado, Zynq and combinations thereof are trademarks of Advanced Micro Devices, Inc. Other product names used in this project are for identification purposes only and may be trademarks of their respective companies.
Comments
Please log in or sign up to comment.