The new Xilinx Vitis tool is a unified software development environment that not only allows us to create embedded solutions but also accelerate applications from the processing system to the programmable logic.
I have been writing about this in my blogs for the last few weeks, looking at each element of Vitis (links).
However, I thought it would be good to provide a in depth walk through of how we can create a Vitis embedded application based on the MiniZed.
Therefore in this project we are going to create a low cost MiniZed motor control application using Vitis.
Hardware BuildThe first step in this process is to create a hardware project in Vivado, for this project we are going to create a new Vivado RTL Project targeting the MiniZed board.
Once the project is open the next stage is to create a new block diagram, which we can add the processing system and elements of our design.
With the block diagram created, we can add in the Zynq7 processing system from the IP catalog. We do this by clicking on the + button on the menu to open the add IP dialog.
Once the Zynq Processing System is added to the block diagram, you will see the Run Block Automation option appear. Running the block automation enables Vivado to configure the processing system for the MiniZed settings, e.g. clocks and DDR configuration.
Following the completion of the Block Automation, double click on the processing system block diagram. This will enable us to configure the processing system, as we desire, in this case we are going to enable a GPIO and the TTC.
On the Peripheral IO Pins tab enable TTC0
Under the MIO configuration -> Application Processor Unit ensure the IO for timer 0 is set to EMIO. This enables the IO to be routed to the programmable logic.
On the same tab we also need to enable a one bit GPIO and route this to the EMIO.
Click on OK and close the re-customization IP dialog
You will notice the Processing System IP block now has a number of new outputs. To make use of these in our application we need to make these external and connected to the IO of the programmable logic.
Right click on the TTC0_WAVE0_OUT and select Make External
Do the same for the GPIO signal, this should result in a diagram like the one below
With these output route out to the programmable logic IO, the next step is to create the HDL wrapper.
Right click on the block diagram in the design sources window and select the Create HDL Wrapper.
This will create a pop up to appear, let Vivado manage the wrapper which is created.
We now also need to create the constraints which define the IO locations on the PL for the timer and the GPIO. We are going to connect these to pins 1 and 2 on the Pmod1 on the MiniZed.
In the design sources window select, add source and Add or Create Constraints.
On the Add or Create Constraints dialog select the create files option
In the dialog which pops up enter a name for the constraints file
In to the created constraints file copy the code below
set_property PACKAGE_PIN M15 [get_ports {TTC0_WAVE0_OUT_0 }];
set_property PACKAGE_PIN L15 [get_ports {GPIO_O_0 }];
set_property IOSTANDARD LVCMOS33 [get_ports TTC0_WAVE0_OUT_0]
set_property IOSTANDARD LVCMOS33 [get_ports GPIO_O_0]
Once the wrapper and constraints are created, we are in a position to implement the design. Select the Generate BitStream Option and wait a few minutes while the design compiles.
Once the bit stream is available, the next step is to export the XSA for use in Vitis. Under file select Export-> Export Hardware.
In the dialog box which is presented select the include bitstream option
We are now ready to open Vitis and write our software application
Software BuildTo open Vitis select Tools->Launch Vitis.
This will launch Vitis, once of the first prompts is to where we wish to store the project workspace. I chose to store it with the Vivdo project I created.
With Vitis open the next step is to create a new application project.
The next stage of the project creation is the definition of the target platform. Click on the "Create a New Platform from hardware (XSA)" tab in the dialog and click on the + symbol.
This will allow us to use the XSA we just exported, use the dialog to navigate to the location of the export XSA.
We will then see the newly created XSA in the list of available projects.
We can then create the domain for the application, select standalone and language of C.
The final stage is to select the template application we desire to use in this case I select the Hello World application.
This will open two elements in Vitis the Platform which contains the FSBL and BSP etc and the Application which contains our application
One key thing to remember is the MiniZed uses UART1 for stdin / stdout we therefore need to update the BSP under the platform to reflect this.
Remember we need to do this for both BSPs which are created. Once these files have been changed remember to build the project.
To pipe clean the process we are therefore going to run the hello world application on the MiniZed.
With the MiniZed connected, select Debug->Configurations
In the debug configuration, make a new system project debug application, make sure the new debug application programs the PL of the FPGA.
Once you are happy with the settings select debug and the application will be downloaded and execution paused at project entry.
When you are ready click on Run and the MiniZed should output a classical "hello world" message to the terminal
If we see this we have done everything correctly and can progress on with more complicated designs.
We are now ready to create our motor control application, to do this delete the contents of the file hello_world.c and copy in the code posted below.
To drive our selected motor we will be using the Pmod HB3 which enables us to drive a PWM signal on to the motor terminals.
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpiops.h"
#include "sleep.h"
#include "xil_exception.h"
#include "xttcps.h"
#include "xscugic.h"
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_0_DEVICE_ID
#define TICK_TIMER_FREQ_HZ 100
#define TTC_TICK_DEVICE_ID XPAR_XTTCPS_0_DEVICE_ID
#define TTC_TICK_INTR_ID XPAR_XTTCPS_0_INTR
static void TickHandler(void *CallBackRef);
int SetupTicker(XTtcPs *TtcPsInst,u16 DeviceID,u16 TtcTickIntrID,XScuGic *InterruptController);
static int SetupInterruptSystem(u16 IntcDeviceID,XScuGic *IntcInstancePtr);
int SetupTimer(u16 DeviceID,XTtcPs *TtcPsInst);
void set_pwm(u32 cycle);
void display_menu();
typedef struct {
u32 OutputHz; /* Output frequency */
XInterval Interval; /* Interval value */
u8 Prescaler; /* Prescaler value */
u16 Options; /* Option settings */
} TmrCntrSetup;
XGpioPs Gpio;
XGpioPs_Config *ConfigPtr;
XTtcPs_Config *TtcConfig;
XTtcPs ttcTimer;
TmrCntrSetup *TimerSetup;
XScuGic InterruptController; /* Interrupt controller instance */
XTtcPs TtcPsInst;
u32 MatchValue;
static TmrCntrSetup SettingsTable={TICK_TIMER_FREQ_HZ, 0, 0, 0};
int main()
{
u8 DutyCycle;
char key_input;
init_platform();
TmrCntrSetup SettingsTable= {TICK_TIMER_FREQ_HZ, 0, 0, 0};
ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr);
XGpioPs_SetDirectionPin(&Gpio, 54, 1);
XGpioPs_SetOutputEnablePin(&Gpio, 54, 1);
XGpioPs_WritePin(&Gpio, 54, 0x1);
printf("www.adiuvoengineering.com\n\r");
printf("DC Motor Control Example\n\r");
SetupInterruptSystem(INTC_DEVICE_ID, &InterruptController);
SetupTicker(&ttcTimer,TTC_TICK_DEVICE_ID,TTC_TICK_INTR_ID,&InterruptController);
while(1){
display_menu();
read(1, (char*)&key_input, 1);
printf("Echo %c\n\r",key_input);
switch (key_input) {
// case 0: // forward
//
// set_pwm(0);
// usleep(1000000);
//
// set_pwm(DutyCycle);
// break;
// case 1: //reverse
//
// //set_pwm(0);
// //usleep(1000000);
// //XGpioPs_WritePin(&Gpio, 54, 0x1);
// //set_pwm(DutyCycle);
// break;
case '1': //stop
set_pwm(0);
break;
case '2': //25%
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;
}
void display_menu()
{
//Clear the screen
printf("\033[2J");
//Display the main menu
printf("*******************************************\n");
printf("**** www.adiuvoengineering.com ****\n");
printf("**** Motor Control Example ****\n");
printf("*******************************************\n");
printf("\n");
printf(" MM10 Motor Control \n");
printf("------------------------------------------\n");
printf("\n");
printf("Select a Speed:\n");
printf(" (1) - Stop\n");
printf(" (2) - 25%\n");
printf(" (3) - 33%\n");
printf(" (4) - 50%\n");
printf(" (5) - 66%\n");
printf(" (6) - 75%\n");
printf(" (7) - 100%\n");
printf("\n");
}
void set_pwm(u32 cycle)
{
u32 MatchValue;
MatchValue = (TimerSetup->Interval * cycle) / 100;
XTtcPs_SetMatchValue(&ttcTimer, 0, MatchValue);
}
int SetupTicker(XTtcPs *TtcPsInst,u16 DeviceID,u16 TtcTickIntrID,XScuGic *InterruptController)
{
int Status;
TmrCntrSetup *TimerSetup;
XTtcPs *TtcPsTick;
TimerSetup = &SettingsTable;
TimerSetup->Options |= (XTTCPS_OPTION_INTERVAL_MODE |
XTTCPS_OPTION_MATCH_MODE | XTTCPS_OPTION_WAVE_POLARITY);
Status = SetupTimer(DeviceID,TtcPsInst);
if(Status != XST_SUCCESS) {
return Status;
}
TtcPsTick = TtcPsInst;
Status = XScuGic_Connect(InterruptController, TtcTickIntrID,
(Xil_InterruptHandler)TickHandler, (void *)TtcPsTick);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
XScuGic_Enable(InterruptController, TtcTickIntrID);
XTtcPs_EnableInterrupts(TtcPsTick, XTTCPS_IXR_INTERVAL_MASK);
XTtcPs_Start(TtcPsTick);
return Status;
}
static int SetupInterruptSystem(u16 IntcDeviceID,XScuGic *IntcInstancePtr)
{
int Status;
XScuGic_Config *IntcConfig;
IntcConfig = XScuGic_LookupConfig(IntcDeviceID);
if (NULL == IntcConfig) {
return XST_FAILURE;
}
Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT,
(Xil_ExceptionHandler) XScuGic_InterruptHandler,
IntcInstancePtr);
Xil_ExceptionEnable();
return XST_SUCCESS;
}
int SetupTimer(u16 DeviceID,XTtcPs *TtcPsInst)
{
int Status;
XTtcPs_Config *Config;
XTtcPs *Timer;
TmrCntrSetup *TimerSetup;
TimerSetup = &SettingsTable;
Timer = TtcPsInst;
Config = XTtcPs_LookupConfig(DeviceID);
if (NULL == Config) {
return XST_FAILURE;
}
Status = XTtcPs_CfgInitialize(Timer, Config, Config->BaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
XTtcPs_SetOptions(Timer, TimerSetup->Options);
XTtcPs_CalcIntervalFromFreq(Timer, TimerSetup->OutputHz,
&(TimerSetup->Interval), &(TimerSetup->Prescaler));
XTtcPs_SetInterval(Timer, TimerSetup->Interval);
XTtcPs_SetPrescaler(Timer, TimerSetup->Prescaler);
return XST_SUCCESS;
}
static void TickHandler(void *CallBackRef)
{
u32 StatusEvent;
/*
* Read the interrupt status, then write it back to clear the interrupt.
*/
StatusEvent = XTtcPs_GetInterruptStatus((XTtcPs *)CallBackRef);
XTtcPs_ClearInterruptStatus((XTtcPs *)CallBackRef, StatusEvent);
//printf("timer\n\r");
/*update the flag if interrupt has been occurred*/
//UpdateFlag = TRUE;
}
The motor we are going to be driving is a little MM10 motor, this will run anti clockwise and will run from a 3v3 rail.
The MiniZed can power this provided we use both USB power inputs, we also need to connect the power on the HB3 to the H Bridge on the Pmod.
We can do this by connecting a flying lead the VCC pin on J5 to the VM connection on the terminals as shown below
Notice the two USB cables providing the power for the MiniZed and the Motor
The motor we are using is the following
This time we are going to not only boot the processor but also program the Flash NVRAM.
Once the application is pasted into the software application and the project up to date. Select the Application project, right click on it and select "Create Boot Image"
The boot image creation dialog will show the order of the files to be included in the configuration process. This includes the FSBL, BIT and Application.
Once the boot image is available, we can use Vitis to write the flash into the memory.
To program the memory we have a very simple option of selecting program flash,
This will take a few minutes to get download the flash, when it completes you will see a image like the one below.
We can then connect the Pmod HB3 to the Minized and drive the application using a terminal window.
Using the terminal window we can then control the motor speed
This project has shown how we can develop a simple motor control application using Vitis in the embedded flow.
See previous projects here.
Additional information on Xilinx FPGA / SoC development can be found weekly on MicroZed Chronicles.
Comments