Heterogeneous system on chips like the Zynq are ideal for motor control applications. The programmable logic means we can implement complex control algorithms and interface with both industry standard and bespoke interfaces.
As I go to a lot of conferences and exhibitions I wanted to create a compact motor control application which show cased how the Zynq can be used for this.
I wanted this demo to be compact and demonstrate the power of the Zynq, even when using one of the smaller Zynq devices. I therefore decided to use the MiniZed which uses the smallest of the single core Zynq devices.
The architecture of the design will be the following.
- MiniZed
- Multi Touch Display Screen
- Pmod HB3
- 12V DC Motor
Controlling the motor is fairly straight forward, to enable a logic level FPGA output to supply a higher voltage needed by most DC motors we use a circuit called a H-Bridge.
The H-Bridge allows us to supply not only a higher voltage but also to be able to control the direction of the motor depending upon a direction signal.
The simplest FPGA H-Bridge control circuit uses two digital control lines, one for the direction and the second to enable the voltage supply to the motor. While this approach works, it provides a very digital motor control, either the motor is running at its full voltage or it is off.
Of course when the maximum voltage is applied to the motor the speed of rotation will be at its maximum. In our applications we need to be able
A more granular control of the motor can be achieved using Pulse Width Modulation (PWM). PWM enables us to control the average voltage across the motor by switching the voltage on and off with a set period repeated pattern.
Increasing the duty cycle results in a higher average voltage across the motor and hence a higher motor speed. Similarly decreasing the duty cycle reduces the average voltage across the motor and lowers the motor speed.
This provides a very granular control of the motor speed.
The hardware design created in Vivado, as we are using the MiniZed we will create a project which uses the MiniZed as the target board.
Into the new project we are going to add in a Zynq processing system and run the block automation to configure the processing system correctly for the MiniZed.
Once we have configured the processing system for the MiniZed we need to customise the processing system for our application. In this case we need to enable the Master General Purpose AXI interface from the processing system to the programmable logic.
To this we will be adding in the following IP
- Pmod MTDS
Once this is added we can run the connection automation, to connect the PmodMTDS into the processing system
As we are using the PmodMTDS to the arduino shield connector, we need to re customise the PmodMTDS IP block to use the SPI and not Pmod.
The PmodMTDS will allow us to have display and control the motor, what we need to do now is implement the direction signal and the PWM output.
To do this we will be using elements within the processing system itself, for the direction we will be using a GPIO signal routed to the EMIO. The PWM signal itself will be provided by the Triple Timer Counter.
Each TTC contains three timers, each of which is able to generate a PWM output. Within the Zynq PS we have two TTCs.
This means we are able to generate up to six PWM signals from the processing system.
If we want to control more than six motors using PWM, we can implement additional PWM IP functions in the programmable logic.
To ensure the PmodMTDS can function correctly we also need to enable interrupts from the programmable logic to the processing system
Once all of this is completed in Vivado the block diagram should look as below.
Once the build is completed we can export the design to SDK and generate the software application
Software ApplicationInside SDK once the hardware platform has been imported, the next step is to create a Board Support Package.
When the dialog is shown to customise the BSP ensure the stdin / stdout are set to ps7_uart_1
Once the BSP is generated, we are ready to create the application project this will be a C++ project.
The software architecture of the design is as follows:
- Configuration of the display and enabling buttons
- Set up the TTC for PWM output
- Enable the interrupt controller and set up the TTC interrupt
- Create the TTC interrupt service routine
- Create the main application
Within the main application, there will be 9 buttons, six of the buttons will provide different speeds while the remaining three will set the direction or stop the motor.
Setting up the tick once the interrupt controller and timer are configured:
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;
}
Setting up the interrupt controller:
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;
}
Setting up the TTC:
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;
}
TTC interrupt service routine:
static void TickHandler(void *CallBackRef)
{
u32 StatusEvent;
StatusEvent = XTtcPs_GetInterruptStatus((XTtcPs *)CallBackRef);
XTtcPs_ClearInterruptStatus((XTtcPs *)CallBackRef, StatusEvent);
}
Main program:
#include "xil_cache.h"
#include "xparameters.h"
#include <stdio.h>
#include <MyDisp.h>
#include <mtds.h>
#include "sleep.h"
#include "xil_exception.h"
#include "xttcps.h"
#include "xscugic.h"
extern "C"{
#include "PmodGPIO.h"
#include "xgpiops.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 PmodGPIO pmoda;
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);
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()
{
char *btnUp = (char*) "Images/IMG_0001.BMP";
char *btnDown = (char*) "Images/IMG_0002.BMP";
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);
printf("www.adiuvoengineering.com\n\r");
printf("DC Motor Control Example\n\r");
mydisp.begin();
mydisp.clearDisplay(clrBlack);
mydisp.setForeground(clrWhite);
mydisp.setPen(penSolid);
mydisp.setForeground(clrBlue);
mydisp.drawImage((char*) "Images/ADIUVO.BMP", 20, 20);
mydisp.drawText((char*) "www.adiuvoengineering.com", 20, 80);
//mydisp.drawText((char*) "DC Motor Control", 10, 250);
mydisp.drawText((char*) "Forward", 10, 140);
mydisp.drawText((char*) "Reverse", 100, 140);
mydisp.drawText((char*) "Stop", 190, 140);
mydisp.drawText((char*) "25%", 10, 200);
mydisp.drawText((char*) "33%", 100, 200);
mydisp.drawText((char*) "50%", 190, 200);
mydisp.drawText((char*) "66%", 10, 260);
mydisp.drawText((char*) "75%", 100, 260);
mydisp.drawText((char*) "100%", 190, 260);
mydisp.createButton(0, btnUp, btnDown, 10, 150);
mydisp.createButton(1, btnUp, btnDown, 100, 150);
mydisp.createButton(2, btnUp, btnDown, 190, 150);
mydisp.createButton(3, btnUp, btnDown, 10, 210);
mydisp.createButton(4, btnUp, btnDown, 100, 210);
mydisp.createButton(5, btnUp, btnDown, 190, 210);
mydisp.createButton(6, btnUp, btnDown, 10, 270);
mydisp.createButton(7, btnUp, btnDown, 100, 270);
mydisp.createButton(8, btnUp, btnDown, 190, 270);
mydisp.enableButton(0, true);
mydisp.enableButton(1, true);
mydisp.enableButton(2, true);
mydisp.enableButton(3, true);
mydisp.enableButton(4, true);
mydisp.enableButton(5, true);
mydisp.enableButton(6, true);
mydisp.enableButton(7, true);
mydisp.enableButton(8, true);
mydisp.drawButton(0, mydisp.isTouched(0) ? BUTTON_DOWN : BUTTON_UP);
mydisp.drawButton(1, mydisp.isTouched(1) ? BUTTON_DOWN : BUTTON_UP);
mydisp.drawButton(2, mydisp.isTouched(2) ? BUTTON_DOWN : BUTTON_UP);
mydisp.drawButton(3, mydisp.isTouched(0) ? BUTTON_DOWN : BUTTON_UP);
mydisp.drawButton(4, mydisp.isTouched(1) ? BUTTON_DOWN : BUTTON_UP);
mydisp.drawButton(5, mydisp.isTouched(2) ? BUTTON_DOWN : BUTTON_UP);
mydisp.drawButton(6, mydisp.isTouched(0) ? BUTTON_DOWN : BUTTON_UP);
mydisp.drawButton(7, mydisp.isTouched(1) ? BUTTON_DOWN : BUTTON_UP);
mydisp.drawButton(8, mydisp.isTouched(2) ? BUTTON_DOWN : BUTTON_UP);
//GPIO_begin(&pmoda,XPAR_PMODGPIO_0_AXI_LITE_GPIO_BASEADDR,0x00);
SetupInterruptSystem(INTC_DEVICE_ID, &InterruptController);
SetupTicker(&ttcTimer,TTC_TICK_DEVICE_ID,TTC_TICK_INTR_ID,&InterruptController);
u8 DutyCycle;
mydisp.drawText((char*) "Forward", 20, 100);
while(1){
mydisp.checkTouch();
switch (mydisp.getButton()) {
case 0: // forward
mydisp.drawText((char*) "Forward", 20, 100);
set_pwm(0);
usleep(1000000);
XGpioPs_WritePin(&Gpio, 54, 0x0);
set_pwm(DutyCycle);
break;
case 1: //reverse
mydisp.drawText((char*) "Reverse", 20, 100);
set_pwm(0);
usleep(1000000);
XGpioPs_WritePin(&Gpio, 54, 0x1);
set_pwm(DutyCycle);
break;
case 2: //stop
mydisp.drawText((char*) "Stop ", 20, 100);
mydisp.drawText((char*) " ", 100, 100);
set_pwm(0);
break;
case 3: //25%
mydisp.drawText((char*) "25%", 100, 100);
DutyCycle = 25;
set_pwm(DutyCycle);
break;
case 4: //33%
mydisp.drawText((char*) "33%", 100, 100);
DutyCycle = 33;
set_pwm(DutyCycle);
break;
case 5: //50%
mydisp.drawText((char*) "50%", 100, 100);
DutyCycle = 50;
set_pwm(DutyCycle);
break;
case 6: //66%
mydisp.drawText((char*) "66%", 100, 100);
DutyCycle = 66;
set_pwm(DutyCycle);
break;
case 7: //75%
mydisp.drawText((char*) "75%", 100, 100);
DutyCycle = 75;
set_pwm(DutyCycle);
break;
case 8: //100%
mydisp.drawText((char*) "100%", 100, 100);
DutyCycle = 100;
set_pwm(DutyCycle);
break;
}
}
return 0;
}
Initial TestingOnce this code was all up and running on the board, before I could connect it to the motor I wanted to double check the PWM was correct and the directions signal change was safe.
If we change the direction at the same time as we are applying PWM signal, then there is potential for a short circuit and damage to be caused.
To test this I connected a oscilloscope to the Pmod outputs and ensured the PWM signal was as expected.
Once I was happy with the PWM output and the safe changing of directions, I connected a motor to the board and tested out all of the different speeds. In the video below you can see the speed changing (and the drive current) as the different speeds are set on the touch screen.
We also see the direction changing and the motor being stopped.
ConclusionThis project has shown how easy it is to set up and get the MiniZed controlling one or several motors.
The MiniZed might be mini but it is mighty
See previous projects here.
Additional Information on Xilinx FPGA / SoC Development can be found weekly on MicroZed Chronicles.
Comments