MATLAB Motor Controller
MATLAB Motor Controller
This project expands on the work done by Matt Turpin and his I2C Motor Controller. The idea is to have the ability to tune your motor controller through a MATLAB GUI interface by changing the proportional and derivative gain constants. 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).
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.
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.
Conclusion
I was never able to implement the external SRAM to store motor values, and in the end had to store them internally which limited me to only 65 data points due to memory constraints. I would suggest in the future to try this project with an 18FXXJXX series PIC since they run at a native voltage of 3.3V and would have an easier time talking to the lower voltage SRAM chips.