Saturday, June 6, 2015

Quadcopter Project

Quadcopter

The quad, controller, and camera


Flight Control Board

The flight control board is based on FreeIMU and built onto an Arduino Mega 2560 running MultiWii. The main goals were to build a customizeable and affordable quadcopter using mainly open source hardware and software, and learn a bit about how it all works. Bugs and design issues seem to have been worked out now, so it looks like this will be the base design for my board. Video

Since it is all open source, adding additional custom components to control lights, sound, servos, etc. is about as easy as it gets. For anybody already familiar with Arduino, it's just a matter of  choosing what customizations to add.

The top picture on the right shows the simplicity of  the FC board, using only a few modules. A previous mock-up of the board was scrapped due to some simple design flaws. The current version attempts to address some of those problems:

a: Although the shield fits tight, secure mounting is a must, and nylon standoffs are used to secure the shield to the Arduino.

b: Here I am using a cheap, single sided prototyping board, but we'll see how long it lasts...

c: The first mock-up used the female header pins as shown on the right of the bottom pic, but they aren't made for Arduinos (too thin), and caused I2C errors and subsequent crashes. The header pins shown on the left are thicker and fit snug every time.

d: The GPS module was left out since GPS nav won't be something I'll be using right away.

The ESCs connect to pins exposed on the top of the board, which are hard to see since the angle of the bottom pic purposely hides my shoddy looking soldering work. The Arduino gets fastened to the frame using rubber mounts taken from broken CD/DVD players to minimize vibration.

Notes:

FC Board:
The board is intially based on a FreeIMU board as mentioned previously, and is actually pretty simple to build at this point, with only 3 modules. The HMC5883L magnetometer module is connected to the aux. I2C lines of the MPU-6050, and running on 3.3V. An APC220 module is connected to the fourth serial port (serial3), and a GPS module can go on serial2 or via I2C if desired. The best part of the flight controller and quadcopter is that it is all based on open source software and hardware, so customization and prototyping like this is actually possible without spending huge amounts of money or time.

FC Board/Arduino Mega Connections:
pin2: front motor
pin3: rear motor
pin5: right motor
pin6: left motor
pin14: APC220 RX pin
pin15: APC220 TX pin
pin16: GPS RX pin
pin17: GPS TX pin
pin18: MPU6050 interrupt
pin20: MPU6050 SDA (I2C)
pin21: MPU6050 SDL (I2C)
pinA0: Voltage divider (battery monitor)
MPU6050: Aux SDA to HMC5883L SDA
MPU6050: Aux SDL to HMC5883L SDL


Main Parts:
1x Turnigy Talon carbon fiber frame
4x Turnigy 20Amp MultiStar ESCs
1x Turnigy ESC programmer
4x NTM Prop Drive Series 28-26A 1200kv/250W Motors
2x Slow Fly 8045 propellers
2x Slow Fly 8045R propellers
1x 4000mAh 3S LiPo battery pack
1x Arduino Mega
1x GY-521 Gyro Module (MPU-6050
1x HMC5883L Magnetometer module 
1x APC220 serial data wireless transceiver
Uses Modified XBox controller for RC (previous post)


XBox Controller:
Prev post. Uses the MultiWii RCSerial protocol
Quad can easily be controled by a PC via RCSerial protocol and APC220 transceiver

Frame/Physical Assembly:

Since the controller and FC board are both custom made, I had to code and then debug the controller during flight in some cases, and had no failsafe at the time. The initial assembly of the central parts of the frame used all nylon bolts. This was to prevent damage to the frame and motors when big crashes happened, since parts would usually break away before breaking or bending much. It currently uses a combination of metal and nylon connectors to provide rigidity and allow for some give on big impacts. As can be seen in the picture above, I have also made my own motor mounts.
ESC Calibration:

One of the problems I ran into in some of the first flights was individual motors cutting out seemingly randomly. The quad uses 20A Turnigy MultiStar ESCs, and gives a choice between slow-down of motors, and hard cutoff when the voltage is low. This caused a bit of frustration since the motors would start cutting out when the voltage was low, regardless of the setting via programming card.

I was confused by the MultiWii calibration code at first since, according to the ESC manual, it would basically just set the programming of the ESCs to the LiPo battery setting, and that hardly seemed like 'calibration':

ESC Programming: 
a: Power ESCs on while receiving max throttle signal
b: These ESC will beep accordingly, move throttle to min to select what option to set in the programming 

The ESCs apparently use this interaction for calibration as well, and it made a big difference once I programmed them this way. The motors seemed to sound a bit smoother afterward, and they slow down nicely to force a landing once the battery is low.

Always remove your propellers before uncommenting this line in 'config.h':
#define ESC_CALIB_CANNOT_FLY 

Propeller Balancing:

Another important factor that was initially overlooked was propeller balancing. This made a big difference in the stability of the craft, as well as reducing vibrations. I followed this tutorial and had some good results on my first shot at it. Videos from the previous post show the vibration evident from unbalanced propellers, and a motor wire breaks in the second one, likely related to the vibration issues.

MultiWii Config and GUI:

The videos in the previous post were shot using the PID settings shown in the pic to the right and with the low-pass filter enabled for the MPU-6050 at 20Hz in 'config.h'. Higher values for the filter makes for 'tighter' controls, but is more susceptible to vibration and affects PID. Mitigating vibration issues should allow a higher setting. 

MultiWii Code:
Code base is MultiWii_dev_r1240
Uses slightly modified version, but can work with original:
  • Supports serial rc cmds with smaller payloads
  • Supports failsafe when using serial rc
  • Supports basic monitoring during flight via processing gui and existing transceiver (prev. post)
  • Still unsure whether to remove or work with test code to control a music player from alarms and controls, so left it in for now (alarm buzzer timing is changed)
  • Longer delay for ESC calibration code specific to MultiStar ESCs (see esc manual below)



Reference Material:
MultiWii - Code w/Modifications
MultiWii - Arduino Mega pin layout
MultiWii Wiki

How Interface and Program Touch Screen Controller (MXB7843) with Microcontroller (PIC18F452)

How to Interface and Program Touch Screen Controller (MXB7843) with Microcontroller (PIC18F452) ? Tutorial

For newbies, please refer to:
Overview of MXB7843 interfacing with Microcontroller


First of all take a look at the schematic of the interfacing circuitry :






In-order to interface touch controller MXB7843 with the Microcontroller you only need to do 4 or 5 connections apart from the coding. Here is the general concept :


I assume that my readers are well aware of SPI communication. (However for newbies, here is some good stuff  on "SPI Tutotial" ).

Timing diagrams of SPI interface with MX7843 is given below:  


Touch screen controller gives the coordinates of X axis and Y axis when touch screen is touched. Interrupt is generated at the MXB7843 PENIRQ pin which tells the microcontroller that screen has been touched thereafter the controller send control bytes to it get the coordinates.Datasheet explain the algorithm to get the coordinates correctly. The procedure is given below:
  • Set up the control byte and call it TB. TB should begin with  the format: 1XXXXXXX binary, where X denotes the particular channel, selected conversion mode
  • Use a general-purpose I/O line on the CPU to pull CS low
  •  Transmit TB and simultaneously receive a byte; call it RB1.
  • Transmit a byte of all zeros ($00 hex) and simultaneously receive byte RB2.
  • Transmit a byte of all zeros ($00 hex) and simultaneously receive byte RB3.
  • Pull CS high.
As far as the control byte is considered, here are the options that one can play around with it :




By now, the formation of control byte remain no more an issue. Once the control byte is formed, what left is the coding, which is explained below for PIC18F452 Microcontroller, developed in C language on C18 compiler with MPLAB as IDE.


This code for touch screen has been designed for the background shown below : 




////////////////////////////////////////////////////////////////////////////////
//   PROJECT:       CONTROL OF ELECTRIC APPLIANCES (SLIDER CONTROL FOR RMS) WITH TOUCH PANEL INTERFACE



//   GROUP MEMBERS:   
//
//   1. Muhammad Asif        2.  Ahmed Fawad       3.  MUHAMMAD Ahmed 
//
//   
//  Code Includes:      TOUCH SCREEN INTERFACING WITH USART
//
//  Dated:  22 APR 2011
//
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
//          INCLUSION OF DIRECTORIES / CRYSTAL INITIALIZATION /    MACROS
/////////////////////////////////////////////////////////////////////////////////




#include<p18F452.h>
#include<p18cxxx.h>
#include<delays.h>
#include<timers.h>
#include<spi.h>
#include<usart.h>
#pragma config OSC=HSPLL , OSCS=OFF
#pragma config BORV=45 , PWRT = ON
#pragma config BOR=ON , WDT =OFF
#pragma config DEBUG=OFF, LVP=OFF , STVR=OFF


#define CS LATCbits.LATC1
#pragma code INT_1 = 0x08


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////




//                    FUNCTION DECLARATIONS




//////////////////////////////////////////////////////////////////////////////




void INTT ();
unsigned char write(unsigned char);
void newline(void);




//////////////////////////////////////////////////////////////////////////////


unsigned char load_select=0;
unsigned char rms_control=0;
unsigned char tx_byte1=0;
unsigned char tx_byte2=0;


//////////////////////////////////////////////////////////////////////////////


         void INT_1 (void)
         {
           _asm
             goto INTT //jump to interrupt routine
           _endasm
         }
         
         #pragma code
         #pragma interrupt INTT


////////////////////////////////////////////////////////////////////////////////////////
//                     INTEERUPT SERVICE ROUTINE
///////////////////////////////////////////////////////////////////////////////////////


   void INTT ()
   {   
   unsigned char dig1,dig2,dig3,dig4,tmp,i,j,k,p,q,temp;   
   unsigned char x;
   unsigned char y; //VARIABLE DECLARATONS
   
   if(INTCONbits.INT0IF)
//   if(INTCON3bits.INT1IF)
   { 
   




while(1)
{
            OpenSPI(SPI_FOSC_64,MODE_00,SMPEND);    // OPENS SPI CONNECTION
                  
                  CS=0;                     // ENABLE MXB7843
      
                  Delay10TCYx(1);               // DELAY
   
                  temp=write(0b10010000);         //  CONTROL BYTE TO RETRIEVE X CORDINATES
                  Delay10TCYx(10);
   
                  x=write(0);
                  Delay10TCYx(10);
   
                  p=write(0);
                  Delay10TCYx(10);
   
                  CS=1;
                  Delay10TCYx(10);
                  CS=0;
   
                  Delay10TCYx(1);
   
                  temp=write(0b11010000);         //   CONTROL BYTE TO RETRIEVE Y CORDINATES
                  Delay10TCYx(10);
               
                  y=write(0);
                  Delay10TCYx(10);
   
                  q=write(0);
                  Delay10TCYx(10);
   
                  CS=1;                     // PULL CS HIGH
   
            CloseSPI();                        //  CLOSE SPI                  
   if(x==0||y==0)
  break;
else 
   putrsUSART("\033[2J");                          // CLEARS THE HYPER-TERMINAL
   
      
      Delay10TCYx(1);


putrsUSART("*** TOUCH SCREEN INTERRUPTED *** ");
   
      newline();
      newline();
   
      Delay10TCYx(1);


      putrsUSART("Co-ordinates = ");


//putrsUSART("*** TOUCH SCREEN INTERRUPTED *** ");
//   
//      newline();
//      newline();
//   
//      Delay10TCYx(1);
//
//      putrsUSART("Co-ordinates = ");
   
     
///////////////////////////////////////////////////////////////////////////
//               DECIMAL  --  BCD  --  ASCII  -- X - CORDINATES
///////////////////////////////////////////////////////////////////////////   
   
   
   dig1=x/1000+48;
   tmp=x%1000;
   dig2=tmp/100+48;
   tmp=tmp%100;
   dig3=tmp/10+48;
   dig4=tmp%10+48;
   
////////////////////////  TRANSMIT X CORDINATES VIA USART   //////////////   
                  
                  TXREG=40;
                  while(TXSTAbits.TRMT==0);
                  TXREG=dig1;
                  while(TXSTAbits.TRMT==0);
                  
                  TXREG=dig2;
                  while(TXSTAbits.TRMT==0);
                  
                  TXREG=dig3;
                  while(TXSTAbits.TRMT==0);
   
                  TXREG=dig4;
                  while(TXSTAbits.TRMT==0);
                  
                  TXREG=44;
                  while(TXSTAbits.TRMT==0);
                  
///////////////////////////////////////////////////////////////////////////
//               DECIMAL  --  BCD  --  ASCII  -- Y - CORDINATES
///////////////////////////////////////////////////////////////////////////   


                  
   
   dig1=y/1000+48;
   tmp=y%1000;
   dig2=tmp/100+48;
   tmp=tmp%100;
   dig3=tmp/10+48;
   dig4=tmp%10+48;
                  
////////////////////////  TRANSMIT Y CORDINATES VIA USART   //////////////   


            
                  TXREG=dig1;
                  while(TXSTAbits.TRMT==0);
                  
                  TXREG=dig2;
                  while(TXSTAbits.TRMT==0);
                  
                  TXREG=dig3;
                  while(TXSTAbits.TRMT==0);
   
                  TXREG=dig4;
                  while(TXSTAbits.TRMT==0);
   
                  TXREG=41;
                  while(TXSTAbits.TRMT==0);
                  
               newline();
   
///////////////////////////////////////////////////////////////////////////
//              CO-ORDINATES MAPPING / CALCULATONS
///////////////////////////////////////////////////////////////////////////   
   
   if(x<74&&y<50)
   {
   putrsUSART("DEVICE 1 IS SELECTED");
   newline();
   putrsUSART("RMS VOLTAGE PERCENTAGE  :   ");


//          if(x<72&&x>60&&y>=39)
//         {
//         putrsUSART("****TOGGLED****");
//         load_select=0b000;
//         rms_control=0b1111;
//         }




         if(x<=49&&y<50)
         {
         putrsUSART("****0****");
         load_select=0b1100;
         rms_control=0b0000;
         }


         else    if(x==50&&y<50)
         {
         putrsUSART("****6****");   
         load_select=0b1100;
         rms_control=0b0001;
         }
      
         else    if(x==51||x==52&&y<50)
         {
         putrsUSART("****12****");
         load_select=0b1100;
         rms_control=0b0010;
         }         


         else  if(x==53&&y<50)
         {
         putrsUSART("****18****");
         load_select=0b1100;
         rms_control=0b0011;
         }         


         else    if(x==54||x==55&&y<50)
         {
         putrsUSART("****26****");
         load_select=0b1100;
         rms_control=0b0100;
         }         


         else   if(x==56&&y<50)
         {
         putrsUSART("****30****");
         load_select=0b1100;
         rms_control=0b0101;
         }         


         else   if(x==57||x==58&&y<50)
         {
         putrsUSART("****36****");   
         load_select=0b1100;
         rms_control=0b0110;
         }      


         else   if(x==59&&y<50)
         {
         putrsUSART("****42****");
         load_select=0b1100;
         rms_control=0b0111;
         }         


         else   if(x==60||x==61&&y<50)
         {
         putrsUSART("****48****");
         load_select=0b1100;
         rms_control=0b1000;
         }   
   
         else   if(x==62&&y<50)
         {
         putrsUSART("****54****");   
         load_select=0b1100;
         rms_control=0b1001;
         }
   
         else   if(x==63||x==64&&y<50)
         {
         putrsUSART("****60****");
         load_select=0b1100;
         rms_control=0b1010;
         }   
   
         else   if(x==65&&y<50)
         {
         putrsUSART("****66****");
         load_select=0b1100;
         rms_control=0b1011;
         }   
   
         else   if(x==66||x==67&&y<50)
         {
         putrsUSART("****72****");
         load_select=0b1100;
         rms_control=0b1100;
         }   
   
         else   if(x==68&&y<50)
         {
         putrsUSART("****78****");
         load_select=0b1100;
         rms_control=0b1101;
         }   
      
         else    if(x==69||x==70&&y<50)
         {
         putrsUSART("****84****");
         load_select=0b1100;
         rms_control=0b1110;
         }


         else    if(x==71||x==72||x==73&&y<50)
         {
         putrsUSART("****96****");
         load_select=0b1100;
         rms_control=0b1111;
         }
      


   }
   else if(x<74&&y>49)
   {
   putrsUSART("DEVICE 2 IS SELECTED");
   newline();
   putrsUSART("RMS VOLTAGE PERCENTAGE  :   ");


//          if(x<73&&y<=70)
//         {
//         putrsUSART("****TOGGLED****");
//         load_select=0b001;
//         rms_control=0b1111;
//         }


        


         if(x==36||x==37&&y>49)
         {
         putrsUSART("****0****");
         load_select=0b1101;
         rms_control=0b0000;
         }


         else    if(x==38||x==39&&y>49)
         {
         putrsUSART("****6****");
         load_select=0b1101;
         rms_control=0b0001;
         }   
      
         else    if(x==40||x==41&&y>49)
         {
         putrsUSART("****12****");
         load_select=0b1101;
         rms_control=0b0010;
         }         


         else  if(x==42||x==43&&y>49)
         {
         putrsUSART("****18****");
         load_select=0b1101;
         rms_control=0b0011;
         }         


         else    if(x==44||x==45&&y>49)
         {
         putrsUSART("****26****");
         load_select=0b1101;
         rms_control=0b0100;
         }         


         else   if(x==46||x==47&&y>49)
         {
         putrsUSART("****30****");   
         load_select=0b1101;
         rms_control=0b0101;
         }      


         else   if(x==48||x==49&&y>49)
         {
         putrsUSART("****36****");
         load_select=0b1101;
         rms_control=0b0110;
         }         


         else   if(x==50||x==51&&y>49)
         {
         putrsUSART("****42****");
         load_select=0b1101;
         rms_control=0b0111;
         }         


         else   if(x==52||x==53&&y>49)
         {
         putrsUSART("****48****");
         load_select=0b1101;
         rms_control=0b1000;
         }   
   
         else   if(x==54||x==55&&y>49)
         {
         putrsUSART("****54****");
         load_select=0b1101;
         rms_control=0b1001;
         }   
   
         else   if(x==56||x==57&&y>49)
         {
         putrsUSART("****60****");
         load_select=0b1101;
         rms_control=0b1010;
         }   
   
         else   if(x==58||x==59&&y>49)
         {
         putrsUSART("****66****");
         load_select=0b1101;
         rms_control=0b1011;
         }   
   
         else   if(x==60||x==61&&y>49)
         {
         putrsUSART("****72****");
         load_select=0b1101;
         rms_control=0b1100;
         }   
   
         else   if(x==62||x==63&&y>49)
         {
         putrsUSART("****78****");
         load_select=0b1101;
         rms_control=0b1101;
         }   
      
         else    if(x==64||x==65&&y>49)
         {
         putrsUSART("****84****");
         load_select=0b1101;
         rms_control=0b1110;
         }


         else    if(x>65&&x<74&&y>49)
         {
         putrsUSART("****96****");
         load_select=0b1101;
         rms_control=0b1111;
         }


   }
   
   else if(x>73&&x<81&&y<37)
   {
   putrsUSART("DEVICE 1 IS TURNED OFF ");
   newline();
     
         load_select=0b0000;
         rms_control=0b1111;
   }


   else if(x>73&&x<81&&y>36&&y<58)
   {
   putrsUSART("DEVICE 1 IS TURNED ON ");
   newline();
     
         load_select=0b1000;
         rms_control=0b1111;
   }


   else if(x>73&&x<81&&y>57&&y<74)
   {
   putrsUSART("DEVICE 2 IS TURNED OFF ");
   newline();
     
         load_select=0b0001;
         rms_control=0b1111;
   }


   else if(x>73&&x<81&&y>73)
   {
   putrsUSART("DEVICE 2 IS TURNED ON ");
   newline();
     
         load_select=0b1001;
         rms_control=0b1111;
   }


  else if(x>80&&y<37)
   {
   putrsUSART("DEVICE 3 IS TURNED OFF ");
   newline();
     
         load_select=0b0010;
         rms_control=0b1111;
   }
   
    else if(x>80&&y>36&&y<58)
   {
   putrsUSART("DEVICE 3 IS TURNED ON ");
   newline();
     
         load_select=0b1010;
         rms_control=0b1111;
   }


   else if(x>80&&y>57&&y<74)
   {
   putrsUSART("DEVICE 4 IS TURNED OFF ");
   newline();
     
         load_select=0b0011;
         rms_control=0b1111;
   }
  


   else if(x>80&&y>73)
   {
   putrsUSART("DEVICE 4 IS TURNED ON ");
   newline();
     
         load_select=0b1011;
         rms_control=0b1111;
   }
   
   //Delay10TCYx(10);
   


   newline();
   newline();








////////////////////////////////////WHAT TO TRANSMIT OVER ETHERNET////////////////////////////////


PORTD=(rms_control<<4)|(load_select);






////////////////////////////////////////////////////////////////////////


}
   
/////////////////////////////////////////////////////////////////////////   
   
//   INTCON3bits.INT1IF=0;
INTCONbits.INT0IF=0;
    }
}
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
//                     INTERRUPT ROUTINE ENDED
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////




/////////////////////////////////////////////////////////////////////////
//                             MAIN - FUNCTION
////////////////////////////////////////////////////////////////////////
void main()




{


unsigned int temp;
   CS=1;
    /////////////////////////INTERRUPT INITIALIZATION///////////////////   
//                  INTCON = 0x80;
//                  INTCON2bits.INTEDG1=0;
//                  INTCON3bits.INT1IE=1;
//                  INTCON3bits.INT1IF=0;
//                  INTCON2bits.RBPU=0;




                  INTCON = 0x80;
                  INTCON2bits.INTEDG0=0;
                  INTCONbits.INT0IE=1;
                  INTCONbits.INT0IF=0;
                  INTCON2bits.RBPU=0;


   //////////////////////PORT INITIALIZATION INITIALIZATION///////////
  




   TRISB=0b11111111;
   TRISD=0x00;
   TRISC=0b10010000;








   ///////////////////////USAER INITIALIZATION///////////////////////
   RCSTAbits.SPEN=1;
   TRISCbits.TRISC6=0;
   TRISCbits.TRISC7=1;
   TRISCbits.TRISC5=0;
   TXSTA=0x20;
   SPBRG=32;


   Delay10TCYx(1);






            OpenSPI(SPI_FOSC_64,MODE_00,SMPEND);    // OPENS SPI CONNECTION
                  
                  CS=0;                     // ENABLE MXB7843
      
                   Delay10TCYx(1);               // DELAY
   
                   temp=write(0b10010000); 


    CS=1;      
   
CloseSPI();




while(1);


}




/////////////////////FUNCTIONS DEFINITION////////////////////////////


      unsigned char write(unsigned char data_out)
      {      
           PIR1bits.SSPIF = 0;            // Clear interrupt flag
           SSPCON1bits.WCOL = 0;         //Clear any previous write collision
          SSPBUF = data_out;              // write byte to SSPBUF register
           if ( SSPCON1 & 0x80 )           // test if write collision occurred
              return ( -1 );              // if WCOL bit is set return negative #
           else
                                   // while( !SSPSTATbits.BF );  // wait until bus cycle complete 
         while( !PIR1bits.SSPIF );        // wait until bus cycle complete  
           
         return (SSPBUF); 
      }




      void newline(void)
      {
         TXREG=13;
         while(TXSTAbits.TRMT==0);
                  
         TXREG=10;
         while(TXSTAbits.TRMT==0);
      }




Download the SOURCE CODE here. You can edit the source code according to your need !