In my previous project post, I went over the basics for driving a brushless DC (BLDC) motor using an FPGA on the AMD Kria™ KD240 Drives Starter Kit with its Motor Accessory Pack. That post ended with a simple Verilog module that used the readings of the embedded Hall sensors in motor's windings to continuously report the rotor's current position and apply the appropriate drive signals to the three phases of the motor to perform 6-step electrical commutation.
As discussed in the theory section and application sections, the sole use of Hall sensor readings to perform the commutation of the BLDC leads to inefficiencies that causes the windings in the motor to heat up, as well as electrical and acoustic noise. This is due to the magnetic fields generated by the coils in the stator (resulting from the applied drive signal to that particular phase) not being aligned with the magnetic field of the permanent magnets in the rotor in the best way to create motion.
To create motion most efficiently, the magnetic field of the coil being driven needs to be orthogonal with the field of the magnet in the rotor. The position of these magnetic fields relative to each other can be interpolated from the back EMF present on each of the three phase windings of the BLDC, and the back EMF can be deduced from measuring the voltage/current of each phase coil.
Measuring the current and voltage on each winding of a BLDC is used in Field Oriented Control (FOC) algorithms where instead of performing commutation based on the positional readout of a Hall effect sensor, commutation is performed when the back EMF of the coil indicates that it is no longer being impacted by the magnetic field of the permanent magnet in the rotor (aka the zero crossing point of the back EMF).
Back EMF measurements can be feed into a closed loop system that transforms it into a torque value and ultimately tries to keep the BLDC rotating at a desired torque value by making adjustments to the commutation to maintain that relative back EMF measurement. This cannot be done with the Hall effect sensors because Hall effect sensors just give you a high or low output, versus the whole picture of the current/voltage change as the rotor magnet passes the stator's windings.
The back EMF current and voltage measurements from a BLDC can be captured via analog-to-digital converter chips where a digital processor like a SOM/FPGA can filter it to remove high-frequency switching components from the signals.
This project will walk through how to interface with the ADC circuit on the KD240 carrier board of the Kria KD240 Drives Starter Kit that monitors the back EMF on the three phases of the BLDC, then stream the raw data samples into user application space via an accelerated kernel to make them available to feed into an FOC control algorithm.
I am starting with the hardware design I created in the previous post using the AMD Vivado™ Design Suite 2024.1, so I will be using the AMD Vitis™ Unified Software Platform 2024.1 and AMD PetaLinux Tools 2024.1 in this project. It's worth noting that the design flow for accelerated applications is going to change significantly with version 2024.2 (which has not been released as of the time of this writing). I will be releasing a new series of "Getting Started" tutorials once 2024.2 is available, so please check my other project write-ups if you plan to adapt this design to 2024.2 or later.
Update Hardware Design for Accelerated PlatformSince I used a standalone/no-OS application to debug the HDL driving the BLDC using the Hall sensor feedback, the first thing to do for this design is to update the Vivado project settings to add the hooks for the accelerated kernel platform by enabling the option Project is an extensible Vitis Platform.
Open Settings from the Flow Navigator window, select the General tab, and enable Project is an extensible Vitis platform.
Click you click Apply/OK, Vivado will ask if you want to create a new Synthesis run or apply the change to a Vitis Platform to the current run.
I opted to create a new synthesis run to preserve the results from the non-platform version. Be sure to check the box to make this new synthesis the active one under Make Active.
This just makes the synthesis time shorter should I switch the design back to not be an extensible Vitis platform. I can simply switch back to that synthesis run.
Speaking of synthesis, while still in the Settings window select the Synthesis tab and disable the option for Incremental synthesis.
As with previous versions of the Vitis IDE (for both the Unified and the Classic versions of the IDE), I found that the design checkpoint files created by incremental synthesis in Vivado clash with the DCP files Vitis tries to create during a build of an accelerated application which causes it to error out. Since this remains true with version 2024.1 it is still necessary to disable incremental synthesis in Vivado before generating the bitstream and exporting the hardware for use in Vitis.
ADC InterfaceWith the Vivado project settings configured to support an accelerated platform, the first step in this design itself is to interface with the ADCs monitoring the back EMF of the BLDC being driven by the Kria KD240 Drives Starter Kit. The KD240 uses a series of three AD7352 ADC chips to sample the voltage and current of the three phases of the BLDC.
The AD7352 is a 12-bit ADC with SPI interface that is read-only, so it's low latency and low effort to stream samples out from and into a controller such as an SOM/FPGA like the Kria K24 SOM. Each AD7352 has two channels with the KD240 routing the voltage measurement of a phase winding on channel A and the current measurement on channel B.
Since the AD7352 doesn't require any input on the SPI interface to configure the conversion (all settings are bootstrapped by the surrounding analog circuitry), it is just a matter of pulling the chip select (CS) line low, providing the SPI clock (SCLK), and registering MISO data in on the rising edge of SCLK. The datasheet for the AD7352 states that it clocks data out on the falling edges of SCLK, which means the Kria needs to clock the data in on the rising edge of SCLK.
For the sake of simplicity and keeping overall data latency low, I decided to implement this interface with the simple state machine shown below in Verilog:
The AD7352 requires 14 SCLK cycles to perform an analog-to-digital conversion and then make the conversion result available on the digital output port. This means that the Kria ultimately receives 14-bit samples with the first two MSBs of the sample being zero.
To address this, I added a counter in the set SCLK high/MISO latch state that counts the total number of SCLKs and puts the incoming serial MISO bit in the appropriate 12-bit sample register:
SclkHigh :
begin
ad7352_sclk <= 1'b1;
if (sclk_period_counter == sclk_half_period) begin
sclk_period_counter <= 2'd00;
state_reg <= SclkLow;
if (sclk_counter < 4'd13) begin // account for 2 leading zeros
phaseA_voltage[sclk_counter] <= adcA_sdata_A;
phaseA_current[sclk_counter] <= adcA_sdata_B;
phaseB_voltage[sclk_counter] <= adcB_sdata_A;
phaseB_current[sclk_counter] <= adcB_sdata_B;
phaseC_voltage[sclk_counter] <= adcC_sdata_A;
phaseC_current[sclk_counter] <= adcC_sdata_B;
end
else begin
phaseA_voltage <= phaseA_voltage;
phaseA_current <= phaseA_current;
phaseB_voltage <= phaseB_voltage;
phaseB_current <= phaseB_current;
phaseC_voltage <= phaseC_voltage;
phaseC_current <= phaseC_current;
end
end
else begin
sclk_period_counter <= sclk_period_counter + 1;
state_reg <= SclkHigh;
end
end
SclkLow :
begin
ad7352_sclk <= 1'b0;
if (sclk_period_counter == sclk_half_period && sclk_counter == 4'd00) begin
sclk_period_counter <= 2'd00;
sclk_counter <= 4'd13;
state_reg <= ChipSelectHigh;
end
else if (sclk_period_counter == sclk_half_period) begin
sclk_period_counter <= 2'd00;
sclk_counter <= sclk_counter - 1;
state_reg <= SclkHigh;
end
else begin
sclk_period_counter <= sclk_period_counter + 1;
sclk_counter <= sclk_counter;
state_reg <= SclkLow;
end
end
To add the SPI interface signals to my constraints file, I was able to translate the pinout from the KD240 carrier board schematic and K24 SOM master pinout XDC file downloaded from the Kria K24 product webpage. See my attached XDC constraints file below.
Stream ADC Samples to PS via Accelerated PlatformOnce each 12-bit sample is read out from the SPI interface, the next step is to stream them to the user space application in the PS (processing system) via an accelerated kernel fed by an AXI Stream interface. This requires another simple state machine to create a master AXI stream interface that puts each ADC sample on the tdata bus as it becomes available.
At this point in the PL (programmable logic) part of the design, I have my SPI state machine with a master AXI stream interface state machine instantiated for each of the 6 data inputs coming from the ADC (the 3 voltage measurements and 3 current measurements):
Which I then added to the block design that already has the motor controller verilog module as described in my previous project post (right-click in free space then select the Add Module... option):
I also added an AXI Stream FIFO to each master AXI Stream output to provide a buffer. The depth of each FIFO is set to 4096, which is the data size the accelerated HLS kernel will need to transfer to the user space application.
To clarify the dataflow in the design so far here is a simplified diagram I drew:
I also tied the motor_ena
GPIO to the start_cnv
input of the ADC SPI state machine so samples are only being gathered when the motor is being driven based on the motor_ena
GPIO being set by the userspace application.
Now that the design itself is completed in Vivado, the next step is to set the platform settings to tell Vitis where the hooks for the HLS kernel are. These are the configuration settings in the Platform Setup tab of the Block Design.
One general purpose master AXI interface is needed for the userspace application to have a general configuration interface port to the HLS kernel via the AXI interconnect (imagine the HLS kernel added as another block to the AXI interconnect like the AXI GPIO and AXI Interrupt Controller that allows the AMD Zynq™ MPSoC a general communication interface):
The rest of the special purpose AXI interfaces on the Zynq MPSoC processing system IP are enabled for the HLS kernel to send data to the ARM® CPU (see dataflow diagram in previous section):
The HLS kernel needs to be told which clock source to use. I personally like putting it on its own clock separate from the rest of the PL design because it forces me to always explicitly handle any clock domain crossings (such as with the motor enable GPIO that also drives the start_cnv input of the ADC SPI state machine) which I've found prevents weird timing errors due to the HLS kernel being a block box to the rest of the PL at this point.
As mentioned in my previous post, I had added the AXI interrupt controller to use its interrupt output as the interrupt for the HLS kernel:
The final tab is just to set the name of the platform and other admin info:
At this point the hardware design in Vivado is complete, so save the block design, run synthesis/implementation, and generate a bitstream. Finally, export the platform to use in Vitis and PetaLinux, ensuring that the Platform Type is set to Hardware and hardware emulation and the option to include bitstream is enabled in the Platform State.
An accelerated/HLS kernel requires an OS on the target device (the Kria K24 SOM) to interface with it. So before the HLS kernel and the final user space application can be created in the Vitis IDE, a PetaLinux project needs to be created and built to generate the embedded Linux image itself for the Kria K24 SOM.
Just like in Vivado where the Kria KD240 Drives Starter Kit can be selected as the target board to autofill the board's default settings, the same can be done in PetaLinux by using the provided BSP for the Kria KD240 Drives Starter Kit from AMD.
The PetaLinux BSPs are available on the same download page as the PetaLinux installer itself, but with the AMD Kria-based development boards be sure to pay attention to which BSP you are downloading. AMD provides a BSP that is just for the Kria K24 SOM itself and a BSP for the Kria KD240 Drives Starter Kit:
Source the PetaLinux tools to the environment, create a new PetaLinux project using the downloaded KD240 BSP, and change directories into the created project directory:
~/KD240_BLDC_Motor$ source /tools/Xilinx/PetaLinux/2024.1/settings.sh
~/KD240_BLDC_Motor$ petalinux-create project -s ../Downloads/xilinx-kd240-starterkit-v2024.1-05230256.bsp
~/KD240_BLDC_Motor$ cd ./xilinx-kd240-starterkit-2024.1/
Import the hardware definition XSA file exported from Vivado using the --get-hw-description
flag:
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1$ petalinux-config --get-hw-description ../
For this particular hardware design, none of the default hardware system settings set by the KD240 BSP need to be modified except the option for leaving TFTPboot enabled if it is not being used.
I found that starting with PetaLinux version 2024.1, leaving TFTPboot enabled if it's not fully configured in the design will result in this build error:
[ERROR] module 'plnx_vars' has no attribute 'CopyDir'
Disable the option to copy final images to tftpboot under Image Packaging Configuration if it will not be utilized:
Using the KD240 BSP to create the PetaLinux project means the only other configuration required here is enabling the accelerated libraries in the root filesystem:
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1$ petalinux-config -c rootfs
The bare minimum of what is needed is encompassed in the petalinux-vitis-acceleration-essential
package group, but I also included other libraries I have found to be helpful/necessary when implementing and debugging accelerated applications in emulation and on hardware.
Filesystem Packages -------> console --> utils --> git ----> [*] git
Filesystem Packages -------> libs -----> xrt --------------> [*] xrt-dev
Filesystem Packages -------> libs -----> opencl-clhpp -----> [*] opencl-clhpp-dev
Petaliunx Package Groups --> packagegroup-petalinux --> [*] packagegroup-petalinux
Petaliunx Package Groups --> packagegroup-petalinux-display-debug --> [*] packagegroup-petalinux--display-debug
Petaliunx Package Groups --> packagegroup-petalinux-vitis-acceleration-essential --> [*] packagegroup-petalinux-vitis-acceleration-essential
Petaliunx Package Groups --> packagegroup-petalinux-opencv --> [*] packagegroup-petalinux-opencv
Petaliunx Package Groups --> packagegroup-petalinux-x11 --> [*] packagegroup-petalinux-x11
Petaliunx Package Groups --> packagegroup-petalinux-gstreamer --> [*] packagegroup-petalinux-gstreamer
Petaliunx Package Groups --> packagegroup-petalinux-v4lutils --> [*] packagegroup-petalinux-v4lutils
After exiting the root filesystem configuration editor, build the PetaLinux project and an SDK for the project. The SDK creates the sysroot needed by the Vitis IDE to to cross compile the application for the K24 SOM:
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1$ petalinux-build
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1$ petalinux-build --sdk
Once the builds have completed, package a wic image for an SD card:
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1$ petalinux-package --wic --images-dir images/linux/ --bootfiles "ramdisk.cpio.gz.u-boot,boot.scr,Image,system.dtb,system-zynqmp-sck-kd-g-revA.dtb"
Then create a new boot binary for the K24 SOM:
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1$ petalinux-package --boot --u-boot --force
Create Custom Platform File DirectoryNow that the hardware design has been exported from Vivado and all of the necessary components have been created with the PetaLinux project, a custom directory structure needs to be created to install the sysroot in and serve as a Kria "dummy" directory for Vitis to cross-compile the application software as well as run emulation.
Since I like to create everything in the top-level directory of the corresponding Vivado project I simply created a folder titled kd240_custom_platform then created the dummy boot and SD card directories within it.
~$ cd ./KD240_BLDC_Motor/
~/KD240_BLDC_Motor$ mkdir -p kd240_custom_platform
~/KD240_BLDC_Motor$ cd ./kd240_custom_platform/
~/KD240_BLDC_Motor/kd240_custom_platform$ mkdir -p pfm
~/KD240_BLDC_Motor/kd240_custom_platform$ cd ./pfm/
~/KD240_BLDC_Motor/kd240_custom_platform/pfm$ mkdir -p boot
~/KD240_BLDC_Motor/kd240_custom_platform/pfm$ mkdir -p sd_dir
Once the custom platform structure is in place the boot script, u-boot, first stage bootloader, device tree, etc. need to be copied over to the dummy boot directory from the PetaLinux build output directory:
~/KD240_BLDC_Motor/kd240_custom_platform/pfm$ cd ../../xilinx-kd240-starterkit-2024.1/images/linux/
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux$ cp ./boot.scr ../../../kd240_custom_platform/pfm/boot/
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux$ cp ./bl31.elf ../../../kd240_custom_platform/pfm/boot/
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux$ cp ./pmufw.elf ../../../kd240_custom_platform/pfm/boot/
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux$ cp ./system.dtb ../../../kd240_custom_platform/pfm/boot/
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux$ cp ./u-boot.elf ../../../kd240_custom_platform/pfm/boot/
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux$ cp ./zynqmp_fsbl.elf ../../../kd240_custom_platform/pfm/boot/
Note: If changes are made to the PetaLinux project and it is rebuilt, don't forget to replace the above files with the updated versions in the boot dummy directory.
Once all of the boot files have been copied over, install the sysroot into the custom platform directory:
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux$ source /tools/Xilinx/PetaLinux/2024.1/settings.sh
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux$ ./sdk.sh -d ../../../kd240_custom_platform/
Again, if changes are made to the PetaLinux project and it is rebuilt, don't forget to rebuild the SDK as well and delete the system directory here in the custom platform directory then re-install it.
Build Device Tree Overlay for Accelerated PlatformThe HLS kernel for an accelerated application is loaded after the K24 SOM has already been booted up and the initial device tree loaded, therefore a device tree overlay needs to be created for it to add the hooks to the hardware within the HLS kernel for the userspace to access it.
To keep everything grouped together, I create my device tree overlays in the top level of the custom platform directory using the Xilinx Command Line Tools (XSCT).
XSCT is available in the Vitis toolset so if they are not already, source the Vitis tools to the environment to be able to launch into XSCT:
~/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux$ cd ../../../kd240_custom_platform/
~/KD240_BLDC_Motor/kd240_custom_platform$ source /tools/Xilinx/Vitis/2024.1/settings64.sh
~/KD240_BLDC_Motor/kd240_custom_platform$ xsct
Once in XSCT, open the hardware design from the exported XSA from Vivado and use the createdts
command to build the device tree source files:
xsct% hsi::open_hw_design ../k24_kd240_design.xsa
xsct% createdts -hw ../k24_kd240_design.xsa -zocl -platform-name kria_kd240 -git-branch xlnx_rel_v2024.1 -overlay -compile -out ./dtg_output
xsct% exit
After exiting XSCT, change directories down into where the programmable logic device tree source include file (pl.dtsi) is located and use the device tree compiler (dtc
) to compile a device tree blob for the programmable logic design (pl.dtbo):
~/KD240_BLDC_Motor/kd240_custom_platform$ cd ./dtg_output/dtg_output/kria_kd240/psu_cortexa53_0/device_tree_domain/bsp/
~/KD240_BLDC_Motor/kd240_custom_platform/dtg_output/dtg_output/kria_kd240/psu_cortexa53_0/device_tree_domain/bsp$ dtc -@ -O dtb -o pl.dtbo pl.dtsi
At this point, we have all of the pieces needed to start developing software in the Vitis Unified IDE. As always, when I create a new workspace I like to create the directory for it in the top level of the corresponding Vivado project the XSA was exported from:
~/KD240_BLDC_Motor$ mkdir -p vitis_workspace
Since I already had a workspace created with the no-OS application components from debugging the Hall sensor HDL (I also used it to debug the HDL for the SPI interface to the AD7352s before I converted it to an accelerated platform), I just used that workspace and created the new platform and application components for the accelerated application in it.
A platform component based on the XSA exported from Vivado is the first thing needed. Select Create Platform Component from the Welcome tab then step through the component setup wizard windows.
I added "accel" in the name of this platform to make it clear that the underlying XSA was created from the version of the Vivado project that is an extensible Vitis Platform. In the Flow tab, point to the XSA for both Hardware and Emulation. Specify Linux for the operation system in the OS and Processor tab.
Once Vitis generates the platform component, open vitis-comp.json
from the Settings folder then navigate linux_psu_cortexa53
.
First click the little door with arrow icon to generate the BIF file, then configure the rest of the directories as follows:
- Pre-Built Image Directory:
/home/<user>/KD240_BLDC_Motor/kd240_custom_platform/pfm/boot
- DTB File:
/home/<user>/KD240_BLDC_Motor/kd240_custom_platform/pfm/boot/system.dtb
- FAT32 Partition Directory:
/home/<user>/KD240_BLDC_Motor/kd240_custom_platform/pfm/sd_dir
- Qemu Data:
/home/<user>/KD240_BLDC_Motor/kd240_custom_platform/pfm/boot
- Qemu Args File: *auto-populates*
- Pmu Args File: *auto-populates*
This is where we point Vitis to the dummy boot and SD card directories and the sysroot created in the custom platform directory in the previous steps.
With the platform component complete, the next step to create the accelerated application component itself. Just to make my life easier, I like to use the vector addition example as a template to start with. This saves some setup time in the Vitis IDE that would otherwise be required with creating/configuring all of the source files for an accelerated application by hand.
In the Welcome tab, select Examples to open the repo of application component templates. The Simple Vector Addition application is under System Design Examples > Vitis Accelerated Libraries Repository > Installed Examples Repository.
Select the Simple Vector Addition application then select Create Application from Template.
Give the application the desired name and select the platform component created in the previous steps. Then point the application to the kernel image and compressed root filesystem from PetaLinux and the sysroot accordingly:
- Kernel Image:
/home/<user>/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux/Image
- Root FS:
/home/<user>/KD240_BLDC_Motor/xilinx-kd240-starterkit-2024.1/images/linux/rootfs.ext4
- Sysroot:
/home/<user>/KD240_BLDC_Motor/kd240_custom_platform/sysroots/cortexa72-cortexa53-xilinx-linux
Once the application component generates all of the source files, the two source files I will be editing for this application are the main function located in vadd.cpp in the application host sources folder, and the HLS kernel C++ code in krnl_vadd.cpp in the HLS kernel's source folder.
But one last thing before writing the actual C++ code - in order to connect the AXI Stream ports added to the platform in the Vivado project, the SP tag specified for each in Vivado needs to be connected to the corresponding input argument of the accelerated kernel. This is done in the binary container configuration file.
Open binary_container_1-link.cfg
in ./vitis_workspace/<application component name>/hw_link
and add the following lines:
[connectivity]
nk=krnl_vadd:1:krnl_vadd_1
stream_connect = M_AXIS_CURR_A:krnl_vadd_1.currentA_in
stream_connect = M_AXIS_CURR_B:krnl_vadd_1.currentB_in
stream_connect = M_AXIS_CURR_C:krnl_vadd_1.currentC_in
stream_connect = M_AXIS_VOLT_A:krnl_vadd_1.voltageA_in
stream_connect = M_AXIS_VOLT_B:krnl_vadd_1.voltageB_in
stream_connect = M_AXIS_VOLT_C:krnl_vadd_1.voltageC_in
The name before the colon is the SP Tag that was specified in the Platform Setup in Vivado and the name after the colon is the name used for the argument passed to the HLS kernel in C++.
Write ADC Samples to File with C++ ApplicationIn the HLS kernel (krnl_vadd.cpp) I am simply reading in raw ADC samples from the AXIS FIFO in 4096 chunks:
void krnl_vadd(int32_t *voltageA_wave_out, int32_t *voltageB_wave_out, int32_t *voltageC_wave_out,
int32_t *currentA_wave_out, int32_t *currentB_wave_out, int32_t *currentC_wave_out, int size,
hls::stream<data_pkt> &voltageA_in, hls::stream<data_pkt> &voltageB_in, hls::stream<data_pkt> &voltageC_in,
hls::stream<data_pkt> ¤tA_in, hls::stream<data_pkt> ¤tB_in, hls::stream<data_pkt> ¤tC_in) {
#pragma HLS INTERFACE mode=m_axi port=voltageA_wave_out depth=WAVE_SIZE bundle=HP0
#pragma HLS INTERFACE mode=m_axi port=voltageB_wave_out depth=WAVE_SIZE bundle=HP0
#pragma HLS INTERFACE mode=m_axi port=voltageC_wave_out depth=WAVE_SIZE bundle=HP0
#pragma HLS INTERFACE mode=m_axi port=currentA_wave_out depth=WAVE_SIZE bundle=HP1
#pragma HLS INTERFACE mode=m_axi port=currentB_wave_out depth=WAVE_SIZE bundle=HP1
#pragma HLS INTERFACE mode=m_axi port=currentC_wave_out depth=WAVE_SIZE bundle=HP1
read_stream(voltageA_wave_out, size, voltageA_in);
read_stream(voltageB_wave_out, size, voltageB_in);
read_stream(voltageC_wave_out, size, voltageC_in);
read_stream(currentA_wave_out, size, currentA_in);
read_stream(currentB_wave_out, size, currentB_in);
read_stream(currentC_wave_out, size, currentC_in);
}
}
And in the main function of the application I am taking those raw ADC samples and writing them to a text file that I can transfer back to my host PC to plot and see what the data looks like.
std::cout << "Launch the kernel." << std::endl;
OCL_CHECK(err, err = q.enqueueTask(krnl_vector_add));
std::cout << "Transfer the data from kernel to source results vector for voltage A." << std::endl;
OCL_CHECK(err, q.enqueueMigrateMemObjects({buffer_voltageA_wave}, CL_MIGRATE_MEM_OBJECT_HOST));
std::cout << "Transfer the data from kernel to source results vector for voltage B." << std::endl;
OCL_CHECK(err, q.enqueueMigrateMemObjects({buffer_voltageB_wave}, CL_MIGRATE_MEM_OBJECT_HOST));
std::cout << "Transfer the data from kernel to source results vector for voltage C." << std::endl;
OCL_CHECK(err, q.enqueueMigrateMemObjects({buffer_voltageC_wave}, CL_MIGRATE_MEM_OBJECT_HOST));
std::cout << "Transfer the data from kernel to source results vector for current A." << std::endl;
OCL_CHECK(err, q.enqueueMigrateMemObjects({buffer_currentA_wave}, CL_MIGRATE_MEM_OBJECT_HOST));
std::cout << "Transfer the data from kernel to source results vector for current B." << std::endl;
OCL_CHECK(err, q.enqueueMigrateMemObjects({buffer_currentB_wave}, CL_MIGRATE_MEM_OBJECT_HOST));
std::cout << "Transfer the data from kernel to source results vector for current C." << std::endl;
OCL_CHECK(err, q.enqueueMigrateMemObjects({buffer_currentC_wave}, CL_MIGRATE_MEM_OBJECT_HOST));
std::cout << "Turn on the motor..." << std::endl;
std::ofstream fd_export;
fd_export.open ("/sys/class/gpio/export");
fd_export << "524";
fd_export.close();
std::ofstream fd_direction;
fd_direction.open ("/sys/class/gpio/gpio524/direction");
fd_direction << "out";
fd_direction.close();
std::ofstream fd_set_high;
fd_set_high.open ("/sys/class/gpio/gpio524/value");
fd_set_high << "1";
fd_set_high.close();
std::cout << "Wait for kernel to finish their operations..." << std::endl;
OCL_CHECK(err, q.finish());
std::cout << "Turn off the motor..." << std::endl;
std::ofstream fd_set_low;
fd_set_low.open ("/sys/class/gpio/gpio524/value");
fd_set_low << "0";
fd_set_low.close();
std::ofstream fd_unexport;
fd_unexport.open ("/sys/class/gpio/unexport");
fd_unexport << "524";
fd_unexport.close();
std::cout << "Opening text file to write voltage A output waveform to..." << std::endl;
FILE *fp_wave_voltageA;
fp_wave_voltageA=fopen("voltageA_wave_out.txt","w");
std::cout << "Writing the output waveform from the results buffer to the text file..." << std::endl;
for (int i = 0; i < DATA_SIZE; i++) {
fprintf(fp_wave_voltageA,"%i\n",prt_voltA[i]);
}
std::cout << "Closing text file..." << std::endl;
fclose(fp_wave_voltageA);
std::cout << "Opening text file to write voltage B output waveform to..." << std::endl;
FILE *fp_wave_voltageB;
fp_wave_voltageB=fopen("voltageB_wave_out.txt","w");
std::cout << "Writing the output waveform from the results buffer to the text file..." << std::endl;
for (int i = 0; i < DATA_SIZE; i++) {
fprintf(fp_wave_voltageB,"%i\n",prt_voltB[i]);
}
std::cout << "Closing text file..." << std::endl;
fclose(fp_wave_voltageB);
std::cout << "Opening text file to write voltage C output waveform to..." << std::endl;
FILE *fp_wave_voltageC;
fp_wave_voltageC=fopen("voltageC_wave_out.txt","w");
std::cout << "Writing the output waveform from the results buffer to the text file..." << std::endl;
for (int i = 0; i < DATA_SIZE; i++) {
fprintf(fp_wave_voltageC,"%i\n",prt_voltC[i]);
}
std::cout << "Closing text file..." << std::endl;
fclose(fp_wave_voltageC);
std::cout << "Opening text file to write current A output waveform to..." << std::endl;
FILE *fp_wave_currentA;
fp_wave_currentA=fopen("currentA_wave_out.txt","w");
std::cout << "Writing the output waveform from the results buffer to the text file..." << std::endl;
for (int i = 0; i < DATA_SIZE; i++) {
fprintf(fp_wave_currentA,"%i\n",prt_currA[i]);
}
std::cout << "Closing text file..." << std::endl;
fclose(fp_wave_currentA);
std::cout << "Opening text file to write current B output waveform to..." << std::endl;
FILE *fp_wave_currentB;
fp_wave_currentB=fopen("currentB_wave_out.txt","w");
std::cout << "Writing the output waveform from the results buffer to the text file..." << std::endl;
for (int i = 0; i < DATA_SIZE; i++) {
fprintf(fp_wave_currentB,"%i\n",prt_currB[i]);
}
std::cout << "Closing text file..." << std::endl;
fclose(fp_wave_currentB);
std::cout << "Opening text file to write current C output waveform to..." << std::endl;
FILE *fp_wave_currentC;
fp_wave_currentC=fopen("currentC_wave_out.txt","w");
std::cout << "Writing the output waveform from the results buffer to the text file..." << std::endl;
for (int i = 0; i < DATA_SIZE; i++) {
fprintf(fp_wave_currentC,"%i\n",prt_currC[i]);
}
std::cout << "Closing text file..." << std::endl;
fclose(fp_wave_currentC);
Obviously this is an overly simplistic software design, and I still need to add the actual signal processing for measuring the back EMF and translating it into a torque value for commutation. But I wanted to first make sure that the raw ADC samples are making it through the system as expected. Once I know ADC data can make it through the fully system, I have the ability to easily add signal processing to either the HLS kernel or the main C++ application with this setup.
You'll probably also notice that I have the GPIO numbers hardcoded for driving the motor_ena
GPIO which is another thing I'd like to clean up, but for debugging/characterization purposes I think this manual method is best. I do want to note here that I did boot this design on the K24 SOM and loaded the accelerated application so I could see what number this GPIO indexed as to add it here... Driving GPIOs from an accelerated application could honestly be a project post all on its own, so I'll just leave it at that for now.
Save all of the source files and build the whole project for hardware by selecting it in the Flow window, then clicking Build All under the Hardware tab. Vitis will show a prompt asking to build the host application and HLS kernel along with the system project.
The system project will take a little while to build, so while waiting on that I updated the QSPI flash on the K24 SOM with the new boot binary built in the PetaLinux project and imaged an SD card with the wic image.
Personally I like to use the boot image recovery tool to update boot binary on K24 SOM's flash. That process is outlined on the Kria wiki here.
I use balenaEtcher to flash SD cards with the wic image:
Then I connect my Kria KD240 to my local router for its network connection.
The GPIO connections for the Hall sensors via Pmod are also still necessary since that's the code still being used to perform commutation and the accelerated application is just streaming the ADC samples.
Transfer Accelerated Application Files to Kria KD240 Drives Starter KitOnce the system project has finished building in Vitis, the final step is to transfer the application output files to the Kria.
Again, for the sake of organization, I create a separate directory in the custom platform to gather all of the files to transfer to the Kria in one place:
~/KD240_BLDC_Motor/kd240_custom_platform$ mkdir -p transfer_to_kd240
~/KD240_BLDC_Motor/kd240_custom_platform$ cd ./transfer_to_kd240/
This is where I also create the description file, shell.json, for the accelerated application:
~/KD240_BLDC_Motor/kd240_custom_platform/transfer_to_kd240$ gedit shell.json
Which looks like this:
{
"shell_type" : "XRT_FLAT",
"num_slots": "1"
}
The device tree blob, host application, and binary container are the other files needed to run the accelerated application on the Kria. The binary container needs its file extension changed from.xclbin to.bin before transferring it through:
~/KD240_BLDC_Motor/kd240_custom_platform/transfer_to_kd240$ cp ../dtg_output/dtg_output/kria_kd240/psu_cortexa53_0/device_tree_domain/bsp/pl.dtbo ./
~/KD240_BLDC_Motor/kd240_custom_platform/transfer_to_kd240$ cp ../../vitis_workspace/adc_data_readback_host/build/hw/adc_data_readback_host ./
~/KD240_BLDC_Motor/kd240_custom_platform/transfer_to_kd240$ cp ../../vitis_workspace/adc_data_readback/build/hw/hw_link/binary_container_1.xclbin ./
~/KD240_BLDC_Motor/kd240_custom_platform/transfer_to_kd240$ mv binary_container_1.xclbin binary_container_1.bin
Then I just use scp
to transfer the files over the LAN from my host PC:
~/KD240_BLDC_Motor/kd240_custom_platform/transfer_to_kd240$ ping 192.168.1.80
~/KD240_BLDC_Motor/kd240_custom_platform/transfer_to_kd240$ scp binary_container_1.bin pl.dtbo shell.json adc_data_readback_host petalinux@192.168.1.80:/home/petalinux/adc_data_readback_files
Run Accelerated Application on the K24 SOMOn the Kria K24 SOM, a directory with the same root name as the accelerated application needs to be created in /lib/firmware/xilinx
then the binary container, device tree blob, and shell file placed in it:
xilinx-kd240-starterkit-20241:~/adc_data_readback_files$ sudo mkdir /lib/firmware/xilinx/adc_data_readback
xilinx-kd240-starterkit-20241:~/adc_data_readback_files$ sudo cp binary_container_1.bin pl.dtbo shell.json /lib/firmware/xilinx/adc_data_readback/
Using the xmutil
command, list the available firmware for accelerated applications to validate the new application shows up.
xilinx-kd240-starterkit-20241:~/adc_data_readback_files$ sudo xmutil listapps
Accelerator Accel_type Base Pid Base_type #slots(PL+AIE) Active_slot
k24-starter-kits XRT_FLAT k24-starter-kits id_ok XRT_FLAT (0+0) 0,
adc_data_readback XRT_FLAT adc_data_readback id_ok XRT_FLAT (0+0) -1
Then unload the default application:
xilinx-kd240-starterkit-20241:~/adc_data_readback_files$ sudo xmutil unloadapp
remove from slot 0 returns: 0 (Ok)
And load the new application:
xilinx-kd240-starterkit-20241:~/adc_data_readback_files$ sudo xmutil loadapp adc_data_readback
xilinx-kd240-starterkit-20241 kernel: OF: overlay: WARNING: memory leak will occur if overlay removed, property: /fpga-region/firmware-name
xilinx-kd240-starterkit-20241 kernel: OF: overlay: WARNING: memory leak will occur if overlay removed, property: /fpga-region/pid
xilinx-kd240-starterkit-20241 kernel: OF: overlay: WARNING: memory leak will occur if overlay removed, property: /fpga-region/resets
xilinx-kd240-starterkit-20241 kernel: OF: overlay: WARNING: memory leak will occur if overlay removed, property: /fpga-region/uid
xilinx-kd240-starterkit-20241 kernel: OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/clocking0
xilinx-kd240-starterkit-20241 kernel: OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/clocking1
xilinx-kd240-starterkit-20241 kernel: OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/afi0
xilinx-kd240-starterkit-20241 kernel: OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/axi_intc_0
xilinx-kd240-starterkit-20241 kernel: OF: overlay: WARNING: memory leak will occur if overlay removed, property: /__symbols__/motor_ena
xilinx-kd240-starterkit-20241 kernel: irq-xilinx: mismatch in kind-of-intr param
adc_data_readback: loaded to slot 0
Make the host application executable, then switch to the root user so the application can access the GPIO in the /sys
directory (yes- I tried adding the petalinux
user to all the access groups to give it the permissions to drive the GPIO, but it simply wouldn't cooperate so that is going to have to be it's own project post as well).
xilinx-kd240-starterkit-20241:~/adc_data_readback_files$ chmod +x adc_data_readback_host
xilinx-kd240-starterkit-20241:~/adc_data_readback_files$ sudo su root
Finally, run the accelerated application. I highly recommend adding print statements in the event that the application freezes up somewhere. I may or may not have spent too much time trying to figure out why the kernel never finished and returned back to the host application. I had forgotten to turn on the motor so there were no ADC samples in the AXIS FIFOs for the kernel to pull out and it was just sitting there forever waiting for the AXIS FIFOs to output data.
xilinx-kd240-starterkit-20241:/home/petalinux/adc_data_readback_files# ./adc_data_readback_host binary_container_1.bin
INFO: Reading binary_container_1.bin
Loading: 'binary_container_1.bin'
Trying to program device[0]: edge
Device[0]: program successful!
Creating buffer objects for each variable...
Setting kernel arguments...
Mapping buffers to pointers...
Launch the kernel.
Transfer the data from kernel to source results vector for voltage A.
Transfer the data from kernel to source results vector for voltage B.
Transfer the data from kernel to source results vector for voltage C.
Transfer the data from kernel to source results vector for current A.
Transfer the data from kernel to source results vector for current B.
Transfer the data from kernel to source results vector for current C.
Turn on the motor...
Wait for kernel to finish their operations...
Turn off the motor...
Opening text file to write voltage A output waveform to...
Writing the output waveform from the results buffer to the text file...
Closing text file...
Opening text file to write voltage B output waveform to...
Writing the output waveform from the results buffer to the text file...
Closing text file...
Opening text file to write voltage C output waveform to...
Writing the output waveform from the results buffer to the text file...
Closing text file...
Opening text file to write current A output waveform to...
Writing the output waveform from the results buffer to the text file...
Closing text file...
Opening text file to write current B output waveform to...
Writing the output waveform from the results buffer to the text file...
Closing text file...
Opening text file to write current C output waveform to...
Writing the output waveform from the results buffer to the text file...
Closing text file...
And just like that, I had 6 text files with raw ADC samples for the voltage and current measurements of the 3 phase windings of the BLDC while it was running:
xilinx-kd240-starterkit-20241:/home/petalinux/adc_data_readback_files# ls -la
-rwxr-xr-x 1 petalinux petalinux adc_data_readback_host
-rw-r--r-- 1 petalinux petalinux binary_container_1.bin
-rw-r--r-- 1 petalinux petalinux currentA_wave_out.txt
-rw-r--r-- 1 petalinux petalinux currentB_wave_out.txt
-rw-r--r-- 1 petalinux petalinux currentC_wave_out.txt
-rw-r--r-- 1 petalinux petalinux pl.dtbo
-rw-r--r-- 1 petalinux petalinux shell.json
-rw-r--r-- 1 petalinux petalinux voltageA_wave_out.txt
-rw-r--r-- 1 petalinux petalinux voltageB_wave_out.txt
-rw-r--r-- 1 petalinux petalinux voltageC_wave_out.txt
Plot ADC Samples in Excel for Back EMF WaveformAfter using scp
to transfer the text files from the Kria back to my host PC, I pulled the data into excel to plot (which I should have just saved the files as CSVs). It's also worth noting that I could have also done this on the Kria itself if I had used the official Ubuntu image instead of creating the Linux image in PetaLinux.
After plotting the voltage and current for each phase together on their own plots, I was able to see that the 4096 chunk captured was from the commutation step where phase A and phase C are being energized, while phase B is left open based on the current measurement:
While debugging the SPI interface, I had placed an ILA on the ADC samples as they were going into the AXI FIFOs to see what the data looked like as it was coming into the FPGA. This shows that the raw ADC samples successfully made it all the way through the system to the text files without a data formatting error changing their value.
Now that I know the samples in the text files are good, I know I have a good platform design to start adding the algorithms to drive commutation and control the back EMF via a sensorless method versus solely relying on the Hall sensors.
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.