Introduction

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 Build

The 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.

Selecting the MiniZed platform

Project setup

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.

Create a new diagram

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.

Adding the processing system

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.

Processing System Added to the Block Diagram

Applying the MiniZed configurations to the processor system

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

Enabling the TTC

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.

Enablign the TTC (timer 0) IO on EMIO

On the same tab we also need to enable a one bit GPIO and route this to the EMIO.

Selecting the GPIO & Routing to the GPIO

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

Making the TTC Output external

Do the same for the GPIO signal, this should result in a diagram like the one below

Completed Output

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.

Creating the HDL wrapper

This will create a pop up to appear, let Vivado manage the wrapper which is created.

Defining who manages the wrapper

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.

Creating constraints

On the Add or Create Constraints dialog select the create files option

Creating new constraints

In the dialog which pops up enter a name for the constraints file

Naming 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.

Implementing the design

Once the bit stream is available, the next step is to export the XSA for use in Vitis. Under file select Export-> Export Hardware.

Exporting the hardware

In the dialog box which is presented select the include bitstream option

Exporting the XSA

We are now ready to open Vitis and write our software application

Software Build

To open Vitis select Tools->Launch Vitis.

Launching 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.

Selecting the workspace

With Vitis open the next step is to create a new application project.

Creating a 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.

Selecting the XSA

This will allow us to use the XSA we just exported, use the dialog to navigate to the location of the export XSA.

Selecting the XSA

We will then see the newly created XSA in the list of available projects.

MiniZed hardware project available to for use

We can then create the domain for the application, select standalone and language of C.

Selecting the Domain

The final stage is to select the template application we desire to use in this case I select the Hello World application.

Selecting a template application

This will open two elements in Vitis the Platform which contains the FSBL and BSP etc and the Application which contains our application

Vitis project

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.

Setting the BSP Stdin / Stdout

Remember we need to do this for both BSPs which are created. Once these files have been changed remember to build the project.

Building 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

Enabling debug

In the debug configuration, make a new system project debug application, make sure the new debug application programs the PL of the FPGA.

Debug configuration

Once you are happy with the settings select debug and the application will be downloaded and execution paused at project entry.

Ready to execute the project in hardware using debug

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.

Result of the PipeCleaning

Motor Application

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

\r");

printf("DC Motor Control Example

\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

\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%

\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("*******************************************

");

printf("**** www.adiuvoengineering.com ****

");

printf("**** Motor Control Example ****

");

printf("*******************************************

");

printf("

");

printf(" MM10 Motor Control

");

printf("------------------------------------------

");

printf("

");

printf("Select a Speed:

");

printf(" (1) - Stop

");

printf(" (2) - 25%

");

printf(" (3) - 33%

");

printf(" (4) - 50%

");

printf(" (5) - 66%

");

printf(" (6) - 75%

");

printf(" (7) - 100%

");

printf("

");

}

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

\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

Powering the motor

Notice the two USB cables providing the power for the MiniZed and the Motor

The motor we are using is the following

Motor specification

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"

Creating the 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.

Configuration the boot creation

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,

Flash download configuration

This will take a few minutes to get download the flash, when it completes you will see a image like the one below.

Flash completion

We can then connect the Pmod HB3 to the Minized and drive the application using a terminal window.

System solution

Using the terminal window we can then control the motor speed

Terminal window for control

Simple motor control working

Wrap Up

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.