Difference between revisions of "MATLAB Motor Controller"

From Mech
Jump to navigationJump to search
Line 52: Line 52:


For the forwarding of MATLAB commands, the values sent through RS232 are put through a large switch to process them.
For the forwarding of MATLAB commands, the values sent through RS232 are put through a large switch to process them.

===Slave PIC===
The Slave PIC used is an 18F4431 which has a quadrature encoder built in. Details on the 4431 can be found on the Robot Drummer project page. The commands sent from the Master PIC are processed in the SSP Interrupt so the PIC knows which values to get or set. The set commands are triggered through a dummy slave send command.

#INT_SSP high
void ssp_isr() {
disable_interrupts(GLOBAL);
state = i2c_isr_state();
if(state < 0x80) //master is sending data
{
if(state == 0)
{
}
if(state == 1) //second received byte is command
{
command = i2c_read();
bitCount=0;
}
if(state >= 2 && state < 6) //other received bytes are data bytes
{
numBuf[state-2] = i2c_read();
}
}
//have to do when state==0x80 since sending from slave gets buggy when more than one
//state is used and cannot be state >= 0x80 since stop bit triggers a state increment
if(state == 0x80) //master is requesting data
{
switch(command) {
case MC_GET_VELOCITY:
i2c_write((int8)(vel_act>>(bitCount*8)));
break;
}
bitCount++;
}
}

The Slave PIC also implements a PD controller.

// Proportional Derivative Control
void PID()
{
updateStatus();
velocity_count += vel_act;
if(velocity_count < 0) {
TargetPosition -= (velocity_count& 0x7FFFFFFF); //negative velocity, need to convert to unsigned and subtract
velocity_count = velocity_count & 0x800000FF;
} else {
TargetPosition += velocity_count;
velocity_count = velocity_count & 0xFF;
}
error = (signed int32) ((signed int32) TargetPosition - (signed int32) TotalCount); // Compute proportional error
derivative = error - last_error; // Compute Derivative Error
last_error = error;
duty = 1000; // Set Duty to 50%
duty += kp*error + kd*derivative; // Adjust Duty of motor (wrt to error)
if (duty > 1990) // Check for Saturation
{
duty = 1990;
}
else if (duty < 10)
{
duty = 10;
}
if(go == 1) {
//record data
if(dataCount < MEM_SIZE) {
if((timeCount % timeInterval) == 0 ) {
motorData[dataCount][0] = TargetPosition;
motorData[dataCount][1] = TotalCount;
dataCount++;
output_d(dataCount);
}
} else {
go = 0;
}
} else {
duty = 1000;
}
disable_interrupts(GLOBAL);
set_power_pwm0_duty((int16)duty); // Adjust PWM in response to PID control
enable_interrupts(GLOBAL);
timeCount++;
}

The gains kp and kd are settable through MATLAB as you can tune your motor controller through the MATLAB interface. The variable 'go' is used to tell the motor when to start motor operations, it also stops the motor when the correct amount of tracking data has been collected. The motor data is also stored in the PID() function. Since the PID fires every 400us, the variable timeInterval is used to break the collection into larger time chunks.


==MATLAB GUI Programming==
==MATLAB GUI Programming==

Revision as of 14:52, 3 July 2009

MATLAB Motor Controller

This project expands on the work done by Matt Turpin and his I2C Motor Controller. In this premise, the slave PIC controls the high speed motor with quadrature encoding. The slave PICs used are the 18f4431 since they have a built in quadrature encoder. The slave PICs implement volatile SRAM to track motor positions to aid in control tuning. The slave PICs are controlled via I2C by the master PIC which is an 18f4520. The master PIC is controlled through MATLAB over RS-232. The motors can be monitored, controlled and tracked through a MATLAB GUI (shown below).

JR-matlab-gui2.jpg

SRAM Memory

SPI SRAM

The project originally was going to use SPI SRAM from Microchip since you can find capacities around 256kbit with only 4 connections. Unfortunately I was unable to get this memory to work with the 5V PICS I was using. I tested the Microchip 23A256 and Winbound SPI SRAM chips. Neither of these chips run at 5V, the Microchip SRAM runs at 1.8V and the Winbound chip runs at 3.3V. I tried voltage dividers, diode drops and pull-ups with diodes and was unsuccessful at communicating with the SRAM chips.For communicating through SPI, there are several other pages on the wiki which discuss it more in detail.

Parallel SRAM

I instead used parallel memory in the final design, with the only downside that this memory requires 26 dedicated pins from the PIC (15 for address, 8 for data I/O and 3 for control). I wrote a small library for communicating with parallel memory located [here]. Notice that since so many pins are required, they will most likely always be different, so the pins are listed in order in arrays data_pins[] and address_pins[]. The parallel ram functions can be found here. Parallel_Mem.h

Code

The code for the project can be found here.

Motor Control Master

Motor Control Slave

Master PIC

The Master PIC receives commands from MATLAB over RS232 and relays them to the slave over I2C. There are numerous articles in this wiki describing both RS232 between MATLAB and I2C communications.

On the 18F4520 PIC, the hardware buffer for RS232 is only 3 characters long, and if that overflows, the RS232 communication shuts down. I found it necessary to implement an additional software buffer which pulled the characters from the RS232 interrupt and placed them into the software buffer.

 #define rs_kbhit              (rs_nextIn != rs_nextOut)

Which is analogous to the kbhit() function and returns true if there are new characters in the buffer.

 #INT_RDA
 void rda_isr() {
    //fired when character is loaded into rs232 buffer (only 3 characters long)
    if(kbhit()) {
       //wait for buffer inputs
       //Load character into software rs232 buffer
       rsBuffer[rs_nextIn] = getc();
       rs_nextIn++;
       if(rs_nextIn>=RS232_BUFFER_SIZE) rs_nextIn = 0;
    }
 }

The RS232 Interrupt pulls the characters from the hardware buffer and places them into the software buffer.

 int8 rs_getc() {
    int8 c;
  
    while(!rs_kbhit);
    c = rsBuffer[rs_nextOut];
    rs_nextOut++;
    if(rs_nextOut > RS232_BUFFER_SIZE) rs_nextOut=0;
  
    return c;
 }

This function is to be used in the place of get_c().

For the forwarding of MATLAB commands, the values sent through RS232 are put through a large switch to process them.

Slave PIC

The Slave PIC used is an 18F4431 which has a quadrature encoder built in. Details on the 4431 can be found on the Robot Drummer project page. The commands sent from the Master PIC are processed in the SSP Interrupt so the PIC knows which values to get or set. The set commands are triggered through a dummy slave send command.

 #INT_SSP high
 void ssp_isr() {
  disable_interrupts(GLOBAL);
  state = i2c_isr_state();
  if(state < 0x80)                 //master is sending data
  {
     if(state == 0)
     {
 
     }
     if(state == 1)                   //second received byte is command
     {
        command = i2c_read();
        bitCount=0;
     }
     if(state >= 2 && state < 6)                   //other received bytes are data bytes
     {
        numBuf[state-2] = i2c_read();
     }
  }
  //have to do when state==0x80 since sending from slave gets buggy when more than one
  //state is used and cannot be state >= 0x80 since stop bit triggers a state increment
  if(state == 0x80)                //master is requesting data
  {
     switch(command) {
        case MC_GET_VELOCITY:
           i2c_write((int8)(vel_act>>(bitCount*8)));
           break;
     }
     bitCount++;
   }
  }

The Slave PIC also implements a PD controller.

 // Proportional Derivative Control
 void PID()                                           
 {
  updateStatus();
 
  velocity_count += vel_act;
  if(velocity_count < 0) {
     TargetPosition -= (velocity_count& 0x7FFFFFFF);      //negative velocity, need to convert to unsigned and subtract
     velocity_count = velocity_count & 0x800000FF;
  } else {
     TargetPosition += velocity_count;
     velocity_count = velocity_count & 0xFF;
  }
 
  
  error = (signed int32) ((signed int32) TargetPosition - (signed int32) TotalCount);             // Compute proportional error
  derivative = error - last_error;                           // Compute Derivative Error
  last_error = error;
  
  duty = 1000;                                       // Set Duty to 50%
  duty += kp*error + kd*derivative;                  // Adjust Duty of motor (wrt to error)
  
 
 
  if (duty > 1990)                                   // Check for Saturation
  {
     duty = 1990;
  }
  else if (duty < 10)
  {
     duty = 10;
  }
 
  if(go == 1) {
    //record data
    if(dataCount < MEM_SIZE) {
       if((timeCount % timeInterval) == 0 ) {
          motorData[dataCount][0] = TargetPosition;
          motorData[dataCount][1] = TotalCount;
          dataCount++;
          output_d(dataCount);
       }
    } else {
       go = 0;
    }
  } else {
     duty = 1000;
  }
  
  disable_interrupts(GLOBAL);
  set_power_pwm0_duty((int16)duty);                  // Adjust PWM in response to PID control
  enable_interrupts(GLOBAL);
  
  timeCount++;
  
 }

The gains kp and kd are settable through MATLAB as you can tune your motor controller through the MATLAB interface. The variable 'go' is used to tell the motor when to start motor operations, it also stops the motor when the correct amount of tracking data has been collected. The motor data is also stored in the PID() function. Since the PID fires every 400us, the variable timeInterval is used to break the collection into larger time chunks.

MATLAB GUI Programming

The GUI for the controller is programmed through the GUIde feature of MATLAB. The code for the GUI is contained [here]. The difference between normal MATLAB programming and GUI programming is that there are no global variables for storing values that are easily creatable or accessible. If you want to access data from a GUI component, you have to through the 'handles' structure, which is a structure containing all of the GUI components on your form. In order to access a string in a text-box, it has to be retrieved in the following manner:

 str = get(handles.textbox-1, 'String')

Also note that in GUI programming, every component has a Callback, which is called when an action is performed on a component (ie a button is pushed). Within the callback, three things are passed, the object itself called 'hobject', 'eventData' which does nothing, and the 'handles' structure. Utilizing callbacks are how you are able to perform actions based on interaction.

If you want to store variables that you want to either access at a later time, or have variables that you can only create once (ie serial ports), then you have to store them in the application data structure. Application data can be placed and accessed in the following way:

 setappdata(hObject, 'serialport', sp)
 sp = getappdata(handles.pushbutton1, 'serialport')

where 'serialport' is the name give in the structure, and sp is what you are storing, in this case an open serial port. Also note that since the application data was stored under pushbutton1, it has to always be accessed through that same component.