element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Path to Programmable 3
  • Challenges & Projects
  • Design Challenges
  • Path to Programmable 3
  • More
  • Cancel
Path to Programmable 3
Blog Building A Balance For Tuning Of Drone Motors With MiniZed - Final
  • Blog
  • Forum
  • Documents
  • Leaderboard
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Path to Programmable 3 to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: guillengap
  • Date Created: 9 Aug 2023 11:04 PM Date Created
  • Views 2237 views
  • Likes 12 likes
  • Comments 5 comments
  • design challenge
  • xilinx
  • vivado
  • Ultra96-V2 Board
  • final project
  • design competition
  • Path to Programmable III
  • vitis
  • programmable
  • minized
Related
Recommended

Building A Balance For Tuning Of Drone Motors With MiniZed - Final

guillengap
guillengap
9 Aug 2023
Building A Balance For Tuning Of Drone Motors With MiniZed - Final

Introduction

Hi everyone! In this blog I will show you my final project of the "Path to Programmable III" challenge. This idea was inspired from the project published by Adam Taylor: Mini But Mighty, the MiniZed and Vitis for Motor Control. As an addition, I have used the LIS2DS12 accelerometer of the MiniZed board to indirectly calculate the angle of inclination of the X axis in relation to the horizontal plane. I have finally used the PID control to correct any tilt errors. This tutorial can serve as a reference to build a drone stabilization system in the future. In the next blog you can check a brief description of my project: Project Description And Upgrade Installation.

image

My project possibly be adjusted to gaming and home themes, since currently drones are used for taking selfies and for club competitions. Why is it important to calibrate drone motors? To have control of the flight of the device. Otherwise your drone may lose control, get lost, crash, flip, become unbalanced, etc. After this experience, my next goal is to build a homemade minidrone using any kind of hardware and software technology.

Hardware Build with Vivado

In my case, my idea is to add a PID controller to manage the speed of the motors of a mini drone by using the LIS2DS12 accelerometer of the MiniZed board. Here I have used the basic configuration shown in chapter 3 of the hardware training course, experience that I repeated here: MiniZed Board Peripheral Configuration. In summary we enable and Map all PS Peripherals as follows:

  1. The Quad SPI flash memory U46 provides 2 Gb of non-volatile storage that can be used for configuration and data storage. Check the box next to Quad SPI Flash:
  2. Also enable USB 0.
  3. Since for MiniZed, the USB 0 Peripheral requires a reset. Enable GPIO MIO by clicking the checkbox next to it.
  4. Also enable SD card.
  5. The UART1 was configured in Lesson 2 of hardware training course.

image

After copying the above project into a new folder called "motor-pwm-accel", I followed the instructions of Adam Taylor's tutorial: "Mini But Mighty, the MiniZed and Vitis for Motor Control":

  1. On the Peripheral IO Pins tab enable TTC0
  2. Under the MIO configuration, ensure the IO for timer 0 is set to EMIO.
  3. On the same tab I also enable a one bit GPIO and route this to the EMIO.
  4. Right click on the TTC0_WAVE0_OUT and select Make External.
  5. Do the same for the GPIO signal.

In addition, it´s necessary to select the II2C_0 port and make external. The Zynq block design looks like this:

image

The block looks as follows:

image

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.

Note: in case you need to make modifications to the hardware design, you must do "Reset output products" and then "Generate output products" in the HDL Wrapper.

image

Create and edit the Constraints file as follows:

io.xdc

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]

# ----------------------------------------------------------------------------
# I2C bus -- ST Micro LIS2DS12TR Accelerometer and temperature
#            Dialog DA9062
# ---------------------------------------------------------------------------- 
# Bank 35
set_property PACKAGE_PIN G15 [get_ports {IIC_0_0_scl_io        }];  # "G15.I2C_SCL"
set_property PACKAGE_PIN F15 [get_ports {IIC_0_0_sda_io         }];  # "F15.I2C_SDA"
# Set the bank voltage for IO Bank 35 to 3.3V
set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 35]];

Select the Generate Bitstream Option and wait a few minutes while the design compiles.

Export the XSA file as the tutorial indicates.

image

Software Build with Vitis IDE

Launch Vitis IDE and create a Workspace. In my case the workspace is C:\xilinx\motor-pwm-accel\Software.

image

Create a new platform project and build it. In my case I name it as Minized_Pwmmotoraacel_Platform

image

Also create the application project “Minized_Pwmmotoraacel_Application”. Here I selected the Hello World application.

image

Now insert the following code in the helloworld.c file

// AUTHOR: GUILLERMO PEREZ

#include <stdio.h>
#include <stdlib.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpiops.h"
#include "sleep.h"
#include "xil_exception.h"
#include "xttcps.h"
#include "xscugic.h"
#include "lis2ds12.h"
#include <math.h> 

#define ACCEL_SCALE_VAL 100/2000
#define ACCEL_OFF_VAL  200

#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);

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;
    u8 DutyCycle2;
    char key_input;
    init_platform();
    sensor_init();

    // pid controller
    float setpoint_angle = 1.0; // reference angle is 0 degrees approx
    float integral = 0.0;
    float dt = 2.05; //  derivative constant
    float Kp = 3.55;  // proportional constant
    float Ki = 0.011;  // integral constant

    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("Tunning drone motor\n\r");
    SetupInterruptSystem(INTC_DEVICE_ID, &InterruptController);
    SetupTicker(&ttcTimer, TTC_TICK_DEVICE_ID, TTC_TICK_INTR_ID, &InterruptController);

    int x_axis_current, z_axis_current;

    while (1)
    {
        if ( pollForAccel( &x_axis_current, &z_axis_current))
        {
        	x_axis_current = abs(x_axis_current);
            z_axis_current = abs(z_axis_current); 
            float x_axis_current_1 = x_axis_current;
            float z_axis_current_1 = z_axis_current;
            float anglexz = (x_axis_current_1 / z_axis_current_1);
            float anglexz2 = atan(anglexz); 
            float anglexz3 = anglexz2*(57.2958);

            float error_angle = abs (setpoint_angle -anglexz3);

        	if (error_angle < 6)
        	{
        		DutyCycle = 55.5; //48
        		DutyCycle2 = 100 - DutyCycle;
        	}

        	else
        	{
                integral =  (integral + (error_angle * dt));
                DutyCycle = ((Kp * error_angle) + (Ki * integral));
                DutyCycle2 = 100 - DutyCycle;
        		if (DutyCycle > 88)
        		{
        			DutyCycle = 88;
        			DutyCycle2 = 100 - DutyCycle;
        		}
        	}
        	set_pwm(DutyCycle);
        	printf("  AccelerationX :  %+5d, AccelerationZ :  %+5d, DutyCycle_M1 : %+5d, DutyCycle_M2 : %+5d, Angle : %+5f\r",x_axis_current, z_axis_current, DutyCycle, DutyCycle2, anglexz3);
        	//usleep(10000);
        }
    }
    cleanup_platform();
    return 0;
}

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;
}

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;
}

Copy the next two files of the library accelerometer into the src project.

image

lis2ds12.c

#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include "platform.h"
#include "xil_printf.h"

#include "xparameters.h"
#include "xiicps.h"
#include "xil_printf.h"

#include "lis2ds12.h"

/* The original code was
 * https://www.element14.com/community/thread/66802/l/i2csensor-app-source-code
 *
 */

/************************** Constant Definitions ******************************/

/*
 * The following constants map to the XPAR parameters created in the
 * xparameters.h file. They are defined here such that a user can easily
 * change all the needed parameters in one place.
 */


#define IIC_DEVICE_ID		XPAR_XIICPS_0_DEVICE_ID

/*
 * The slave address to send to and receive from.
 */
#define IIC_SLAVE_ADDR		0x55
#define IIC_SCLK_RATE		100000

XIicPs Iic;		/**< Instance of the IIC Device */

#define MINIZED_MOTION_SENSOR_ADDRESS_SA0_HI  0x1D /* 0011101b for LIS2DS12 on MiniZed when SA0 is pulled high*/

#define TEST_BUFFER_SIZE 32
/*
 * The following buffers are used in this example to send and receive data
 * with the IIC.
 */
u8 SendBuffer[TEST_BUFFER_SIZE];    /**< Buffer for Transmitting Data */
u8 RecvBuffer[TEST_BUFFER_SIZE];    /**< Buffer for Receiving Data */



int IicPsMasterPolledExample()
{
	int Status;
	XIicPs_Config *Config;
	int Index;

	/*
	 * Initialize the IIC driver so that it's ready to use
	 * Look up the configuration in the config table,
	 * then initialize it.
	 */
	Config = XIicPs_LookupConfig(IIC_DEVICE_ID);
	if (NULL == Config) {
		return XST_FAILURE;
	}

	Status = XIicPs_CfgInitialize(&Iic, Config, Config->BaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/*
	 * Perform a self-test to ensure that the hardware was built correctly.
	 */
	Status = XIicPs_SelfTest(&Iic);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/*
	 * Set the IIC serial clock rate.
	 */
	XIicPs_SetSClk(&Iic, IIC_SCLK_RATE);

	return XST_SUCCESS;

}



u8 LIS2DS12_WriteReg(u8 Reg, u8 *Bufp, u16 len)
{
	SendBuffer[0] = Reg;
	int Status;
	for (int ByteCount = 1;ByteCount <= len; ByteCount++)
	{
		SendBuffer[ByteCount] = Bufp[ByteCount-1];
	}

	/*
	 * Send the buffer using the IIC and ignore the number of bytes sent
	 * as the return value since we are using it in interrupt mode.
	 */
	Status = XIicPs_MasterSendPolled(&Iic, SendBuffer,	 (len+1), MINIZED_MOTION_SENSOR_ADDRESS_SA0_HI);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/*
	 * Wait until bus is idle to start another transfer.
	 */
	while (XIicPs_BusIsBusy(&Iic)) {
		/* NOP */
	}

	return XST_SUCCESS;
}

u8 LIS2DS12_ReadReg(uint8_t Reg, uint8_t *Bufp, uint16_t len)
{
	int Status;
	SendBuffer[0] = Reg;

	/*
	 * Send the buffer using the IIC and ignore the number of bytes sent
	 * as the return value since we are using it in interrupt mode.
	 */
	Status = XIicPs_MasterSendPolled(&Iic, SendBuffer,1, MINIZED_MOTION_SENSOR_ADDRESS_SA0_HI);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	/*
	 * Wait until bus is idle to start another transfer.
	 */
	while (XIicPs_BusIsBusy(&Iic)) {
		/* NOP */
	}

	Status = XIicPs_MasterRecvPolled(&Iic, Bufp, len, MINIZED_MOTION_SENSOR_ADDRESS_SA0_HI);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}

	while (XIicPs_BusIsBusy(&Iic)) {
		/* NOP */
	}

	return XST_SUCCESS;
}


bool isSensorConnected()
{
	uint8_t who_am_i = 0;
	uint8_t send_byte;

	LIS2DS12_ReadReg(LIS2DS12_ACC_WHO_AM_I_REG, &who_am_i, 1);
	printf("With I2C device address 0x%02x received WhoAmI = 0x%02x\r\n", MINIZED_MOTION_SENSOR_ADDRESS_SA0_HI, who_am_i);
	if (who_am_i != LIS2DS12_ACC_WHO_AM_I)
	{
		//maybe the address bit was changed, try the other one:
		LIS2DS12_ReadReg(LIS2DS12_ACC_WHO_AM_I_REG, &who_am_i, 1);
		printf("With I2C device address 0x%02x received WhoAmI = 0x%02x\r\n", MINIZED_MOTION_SENSOR_ADDRESS_SA0_HI, who_am_i);
	}
	send_byte = 0x00; //No auto increment
	LIS2DS12_WriteReg(LIS2DS12_ACC_CTRL2, &send_byte, 1);

	//Write 60h in CTRL1	// Turn on the accelerometer.  14-bit mode, ODR = 400 Hz, FS = 2g
	send_byte = 0x60;
	LIS2DS12_WriteReg(LIS2DS12_ACC_CTRL1, &send_byte, 1);
	printf("CTL1 = 0x60 written\r\n");

	//Enable interrupt
	send_byte = 0x01; //Acc data-ready interrupt on INT1
	LIS2DS12_WriteReg(LIS2DS12_ACC_CTRL4, &send_byte, 1);
	printf("CTL4 = 0x01 written\r\n");
	return true;

}
bool sensor_init()
{
	if (XST_SUCCESS != IicPsMasterPolledExample())
	{
		return false;
	}
	isSensorConnected();

	return true;
}


int u16_2s_complement_to_int(u16 word_to_convert)
{
	u16 result_16bit;
	int result_14bit;
	int sign;

	if (word_to_convert & 0x8000)
	{ //MSB is set, negative number
		//Invert and add 1
		sign = -1;
		result_16bit = (~word_to_convert) + 1;
	}
	else
	{ //Positive number
		//No change
		sign = 1;
		result_16bit = word_to_convert;
	}
	//We are using it in 14-bit mode
	//All data is left-aligned.  So convert 16-bit value to 14-but value
	result_14bit = sign * (int)(result_16bit >> 2);
	return(result_14bit);
} //u16_2s_complement_to_int()


bool pollForAccel(int *x, int *z)
{
		int iacceleration_X;
		int iacceleration_Y;
		int iacceleration_Z;
		u8 read_value_LSB;
		u8 read_value_MSB;
		u16 accel_X;
		u16 accel_Y;
		u16 accel_Z;
		u8 accel_status;
		u8 data_ready;

		data_ready = 0;

		 //wait for DRDY
		LIS2DS12_ReadReg(LIS2DS12_ACC_STATUS, &accel_status, 1);
		data_ready = accel_status & 0x01; //bit 0 = DRDY
		if (!data_ready)
			return false;

		//wait for DRDY


		//Read X:
		LIS2DS12_ReadReg(LIS2DS12_ACC_OUT_X_L, &read_value_LSB, 1);
		LIS2DS12_ReadReg(LIS2DS12_ACC_OUT_X_H, &read_value_MSB, 1);
		accel_X = (read_value_MSB << 8) + read_value_LSB;
		iacceleration_X = u16_2s_complement_to_int(accel_X);
		//Read Y:
		LIS2DS12_ReadReg(LIS2DS12_ACC_OUT_Y_L, &read_value_LSB, 1);
		LIS2DS12_ReadReg(LIS2DS12_ACC_OUT_Y_H, &read_value_MSB, 1);
		accel_Y = (read_value_MSB << 8) + read_value_LSB;
		iacceleration_Y = u16_2s_complement_to_int(accel_Y);
		//Read Z:
		LIS2DS12_ReadReg(LIS2DS12_ACC_OUT_Z_L, &read_value_LSB, 1);
		LIS2DS12_ReadReg(LIS2DS12_ACC_OUT_Z_H, &read_value_MSB, 1);
		accel_Z = (read_value_MSB << 8) + read_value_LSB;
		iacceleration_Z = u16_2s_complement_to_int(accel_Z);

		if (x)
		{
			*x = iacceleration_X;
		}

//		if (y)
//		{
//			*y = iacceleration_Y;
//		}

		if (z)
		{
			*z = iacceleration_Z;
		}

	//	printf("  Acceleration = X: %+5d, Y: %+5d, Z: %+5d\r\n",iacceleration_X, iacceleration_Y, iacceleration_Z);
//		printf("  Acceleration = X: %+5d, Y: %+5d, Z: %+5d\r",iacceleration_X, iacceleration_Y, iacceleration_Z);

		return true;
}

lis2ds12.h

#include <stdbool.h>
extern bool sensor_init(void);
extern bool pollForAccel(int *x, int *y );




/*
 * The following constant defines the address of the IIC device on the IIC bus.  Note that since
 * the address is only 7 bits, this  constant is the address divided by 2.
 */
#define MAGNETOMETER_ADDRESS  0x1E /* LIS3MDL on Arduino shield */
#define MINIZED_MOTION_SENSOR_ADDRESS_SA0_LO  0x1E /* 0011110b for LIS2DS12 on MiniZed when SA0 is pulled low*/
#define MINIZED_MOTION_SENSOR_ADDRESS_SA0_HI  0x1D /* 0011101b for LIS2DS12 on MiniZed when SA0 is pulled high*/

#define LIS2DS12_ACC_WHO_AM_I         0x43
/************** Device Register  *******************/
#define LIS2DS12_ACC_SENSORHUB_OUT1  	0X06
#define LIS2DS12_ACC_SENSORHUB_OUT2  	0X07
#define LIS2DS12_ACC_SENSORHUB_OUT3  	0X08
#define LIS2DS12_ACC_SENSORHUB_OUT4  	0X09
#define LIS2DS12_ACC_SENSORHUB_OUT5  	0X0A
#define LIS2DS12_ACC_SENSORHUB_OUT6  	0X0B
#define LIS2DS12_ACC_MODULE_8BIT  	0X0C
#define LIS2DS12_ACC_WHO_AM_I_REG  	0X0F
#define LIS2DS12_ACC_CTRL1  	0X20
#define LIS2DS12_ACC_CTRL2  	0X21
#define LIS2DS12_ACC_CTRL3  	0X22
#define LIS2DS12_ACC_CTRL4  	0X23
#define LIS2DS12_ACC_CTRL5  	0X24
#define LIS2DS12_ACC_FIFO_CTRL  	0X25
#define LIS2DS12_ACC_OUT_T  	0X26
#define LIS2DS12_ACC_STATUS  	0X27
#define LIS2DS12_ACC_OUT_X_L  	0X28
#define LIS2DS12_ACC_OUT_X_H  	0X29
#define LIS2DS12_ACC_OUT_Y_L  	0X2A
#define LIS2DS12_ACC_OUT_Y_H  	0X2B
#define LIS2DS12_ACC_OUT_Z_L  	0X2C
#define LIS2DS12_ACC_OUT_Z_H  	0X2D
#define LIS2DS12_ACC_FIFO_THS  	0X2E
#define LIS2DS12_ACC_FIFO_SRC  	0X2F
#define LIS2DS12_ACC_FIFO_SAMPLES  	0X30
#define LIS2DS12_ACC_TAP_6D_THS  	0X31
#define LIS2DS12_ACC_INT_DUR  	0X32
#define LIS2DS12_ACC_WAKE_UP_THS  	0X33
#define LIS2DS12_ACC_WAKE_UP_DUR  	0X34
#define LIS2DS12_ACC_FREE_FALL  	0X35
#define LIS2DS12_ACC_STATUS_DUP  	0X36
#define LIS2DS12_ACC_WAKE_UP_SRC  	0X37
#define LIS2DS12_ACC_TAP_SRC  	0X38
#define LIS2DS12_ACC_6D_SRC  	0X39
#define LIS2DS12_ACC_STEP_C_MINTHS  	0X3A
#define LIS2DS12_ACC_STEP_C_L  	0X3B
#define LIS2DS12_ACC_STEP_C_H  	0X3C
#define LIS2DS12_ACC_FUNC_CK_GATE  	0X3D
#define LIS2DS12_ACC_FUNC_SRC  	0X3E
#define LIS2DS12_ACC_FUNC_CTRL  	0X3F

If you build the project you may get errors because you haven't added the math.h library properly. Then, with right click open properties of application project, and insert the letter m in the libraries tab as shown below:

image

This code helps us to calculate the inclination angle indirectly with the accelerometer of the MiniZed board. If the angle is greater than 5 degrees then the PID control corrects the error so that the motors keep the drone within this threshold. Next I give you more details on how to calculate the angle of inclination with the accelerometer, and how to adjust the PID controller.

Finding Angle Of Inclination with the LIS2DS12 accelerometer

An inclinometer is a device that allows us to measure the inclination in degrees of a surface. Through the accelerometer of the LIS2DS12 we can measure the acceleration of a moving system or device. The LIS2DS12 is an ultra-low-power, high-performance three-axis linear accelerometer belonging to the “pico” family which leverages on the robust and mature manufacturing processes already used for the production of micromachined accelerometers. The LIS2DS12 has user-selectable full scales of ±2g/±4g/±8g/±16g and is capable of measuring accelerations with output data rates from 1 Hz to 6400 Hz.

image

By geometry, the angle drawn (Θ) by the weight vector W with the Wz axis is equal to the angle (Θ) of the inclined plane of the diagram. So the angle theta (Θ) is equal to the arc tangent of (Wx/Wz).

image

The idea is quite simple, we are all under the influence of gravity, the accelerometer will allow us to know the direction of this force and we will only apply some simple trigonometric formulas to calculate the inclination of the sensor.

image

The accelerometer as such never knows its position. However he knows the components of the force of gravity. We can use the values of the force that is applied to the components to know the angle, using a simple inverse trigonometric function:

image

In my case, I will use the X axis of the accelerometer, so the Y axis accelerometer is zero, and the formula reduces to:

image

The part of the code that calculates the tilt angle is:

    while (1)
    {
        if ( pollForAccel( &x_axis_current, &z_axis_current))
        {
        	x_axis_current = abs(x_axis_current);
            z_axis_current = abs(z_axis_current); 
            float x_axis_current_1 = x_axis_current;
            float z_axis_current_1 = z_axis_current;
            float anglexz = (x_axis_current_1 / z_axis_current_1);
            float anglexz2 = atan(anglexz); 
            float anglexz3 = anglexz2*(57.2958);

PID Controller

A proportional–integral–derivative controller (PID controller) is a control loop mechanism employing feedback that is widely used in industrial control systems and a variety of other applications requiring continuously modulated control. A PID controller continuously calculates an error value e(t) as the difference between a desired set point (SP) and a measured process variable (PV) and applies a correction based on proportional, integral, and derivative terms (denoted P, I, and D respectively). https://en.wikipedia.org/wiki/PID_controller. You can find many references of PID controller applications, here I show you some personal ones and others:

  • https://www.hackster.io/guillengap/pid-light-meter-controller-724672;
  • https://hackaday.io/project/181361-uv-sanitizing-autonomous-robot/log/197114-step-7-pid-controller;  
  • http://electronoobs.com/eng_robotica_tut6.php

For the balance we will use just one axis. So, I've choose the x angle to implement the PID controller. That means that the x axis of the accelerometer has to be parallel to the balance. In my case, MiniZed board generates a PWM signal of 100 Hz, and the control data are:

    // pid controller
    float setpoint_angle = 1.0; // reference angle 
    float integral = 0.0;
    float dt = 2.05; 
    float Kp = 3.55;
    float Ki = 0.011;

The simulation of this PID system would be the following:

image

When the system is balanced, the duty cycle of both motors is 50% approx (left and right).

First, we calculate the error between the desired angle and the real measured angle.

float error_angle = abs (setpoint_angle -anglexz3);

The MiniZed board uses PID controller to maintain on the desire angle the balance. If the left side increase the desire angle, then it can decrease the speed of the right motor and increase the speed of the left motor, to make the balance move to the right side.

                integral =  (integral + (error_angle * dt));
                DutyCycle = ((Kp * error_angle) + (Ki * integral));
                DutyCycle2 = 100 - DutyCycle;

If the right side increases the desire angle, then it can decrease the speed of the left motor and increase the speed of the right motor, to make the balance move to the left side.

In other words, on the left side motor I generate a PWM signal called phase, and on the right side motor I generate an antiphase PWM signal.

In my solution in the motor on the left side I have generated a phase PWM signal by software, and in the motor on the right side I have generated an antiphase PWM signal by hardware as you will see in the schematic diagram in the next section.

Hardware Assembly

Below I show you the schematic diagram of my project:

image

The docs for most PMOD look like this, with pin 1 on the right side:

image

I also attach the pinout diagram of the TB662FNG Driver and 40106 Schmitt Trigger.

image

First we are going to build the structure of the balance. In my case I used an aluminum alloy due to its low weight, just like real airplanes. Below I show you the pieces used with their dimensions.

image

Assembling the scale is very easy; you only need a drill and several screws. You also have to add a piece of wood as a support with dimensions 140(L) x 70(W) x 15(H) mm.

image

The next step is to assemble the DC motors with the propellers. These motors are generic 3V. In the tests and conclusions I will comment my experience with this type of engine.

image

I have attached the motors and propellers using pliers and drilling the ends of the aluminum shaft.

image

According to the schematic diagram we should solder the TB662FNG driver and the 40106 Schmitt Trigger as shown below.

image

In the same way, I have assembled the Minized board and the hardware devices on a plastic base of 180(L) x 80(W) mm.

image

The device mounted on the scale is shown below; I have put two boxes on the sides to support the motor shaft when they are turned off.

image

Test

In the video below I show you a test carried out to trace the PWM signals generated from the prototype before mounting it on the scale.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

Finally, we start the debugger and run the code. Below you can see the final test video.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

Conclusion

This project is didactic to learn how to calibrate the drone motors, below I show you some tips that can be useful to repeat the experience:

  1. In my case I have used a PWM signal to generate the PWM signals of the two motors since the PWM signal of the second motor is the antiphase of the first motor.
  2. When the axis of the two motors is in balance, the PWM signals of both motors are 50% (X-accelerometer of the LIS2DS12).
  3. When the system is unbalanced, the PID controller calculates the angle error and increases the duty cycle on the lower motor and decreases the duty cycle on the upper motor until the angle is approximately 1 degree.
  4. You can experiment with more PWM signals, but keep in mind that the PID controller calculates the angle error, which will add on one PWM signal, and subtract on the other PWM signal.
  5. In my case the PID control constants were the following: dt = 2.05 (derivative), Kp = 3.55 (proportional), and Ki = 0.011(integral). However you can use other values according to your design.

Github repository

  • https://github.com/guillengap/tuning-drone-motors-minized

References

  • https://www.st.com/en/mems-and-sensors/lis2ds12.html
  • https://www.hackster.io/adam-taylor/mini-but-mighty-the-minized-and-vitis-for-motor-control-9c376b#toc-wrap-up-4
  • https://www.hackster.io/MichalsTC/mini-but-mighty-motor-control-via-builtin-accelerometer-221619
  • http://electronoobs.com/eng_robotica_tut6.php
  • Sign in to reply
  • javagoza
    javagoza over 2 years ago in reply to guillengap

    I wish you to recover soon. Covid-19 has also rebounded in Spain but now it is not in the news, you find out from nearby cases.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • guillengap
    guillengap over 2 years ago in reply to javagoza

    javagoza  You said it right, this was a challenge! ... Three months is a long time, however I planned my strategy taking time from the training course to finish this project... My friend! I wish you good luck and I hope you are in good health because of the consequences of the covid- 19 (aftermath) have reduced the capabilities of many people like me.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • javagoza
    javagoza over 2 years ago

    Great job on the project, guillengap! I completely agree that the training course was quite demanding and the deadline for the project was tight. It's been the most challenging experience I've had at element14. Perhaps it would be beneficial to change the format for future learning program paths.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • guillengap
    guillengap over 2 years ago in reply to prashanthgn.engineer

    Thanks for your positive comment! I hope it will serve as a model reference for other users.

    Kind regards.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • prashanthgn.engineer
    prashanthgn.engineer over 2 years ago

    Great Project blog.

    Well explained.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube