Difference between revisions of "E-Puck Color Sensing Project"

From Mech
Jump to navigationJump to search
 
 
(36 intermediate revisions by one other user not shown)
Line 1: Line 1:
[[Image:SteelToePic2.jpg|right|The 'Steel Toe' programmable stiffness joint|thumb|400px]]
[[Image:e-puck_color_sensing_project.jpg|right|e-Puck with the Zigbee module add-on board and red color sensor.|thumb|400px]]


==Overview==
==Overview==
Project by: James Yeung, Electrical Engineering, Class of 2009.<br>
The goal of this project is to add a color sensor onto the e-Puck, have it roam around on the ground with a projected image over it and send data back to a PC using RS232 protocol via bluetooth. Other capabilities include dead reckoning and position/heading update from an outside source. After the robot covers the full image, the host PC will display the reconstructed image and plot the error from the actual image. This project covers color sensing, robot control and wireless communication by RS232 via bluetooth with the e-Puck using MATLAB.
Last updated: June 16, 2008

The goal of this project was to have the e-Puck roll around on the floor under a projected image, take sensor readings with one of the R G or B sensors, send back the position and color data to a listening PC by RS232 protocol via bluetooth, and occasionally receive from the PC new (x,y,theta) data on the actual position of the e-Puck (between receiving this data, the robot keeps track of its position by dead reckoning; upon receiving the data, the robot resets its estimated position to the new data). The robot had to roll around to cover the whole projected image (which is unchanging), and at the end of the run, the host PC had to display the reconstructed image and plot the error from the actual image. On the host PC side, MATLAB was used as an interface to talk to the e-Puck and generate the appropriate graphs.


==Circuitry For Color Sensor==
==Circuitry For Color Sensor==
The photo diodes that I worked with were from Hamamatsu. These were the ones that were considered.
[[Image:E_Puck_Color_Sensing_Circuit_1.gif|right|Circuit diagram for attempt 1.|thumb|400px]]
* S9702 - RGB sensor (1mm^2 active area) - ~$9 (min 10)
* S9032-2 - RGB sensor (2mm^2 active area) - ~$15 (min 5)
* S6430-01 - Red color sensor (4mm^2 active area) - ~$11 (min 10)
* S6429-01 - Green color sensor (4mm^2 active area) - ~$11 (min 10)
* S6428-01 - Blue color sensor (4mm^2 active area) - ~$11 (min 10)


I mainly worked with the S9702 and the S6400 series sensors.
[[Image:progstiffjointtorquecompdiag.jpg|thumb|450px|Diagram showing the variables used to compute the torque applied by the spring|right]]
===Attempt 1===
I started by simply hooking the sensors with the circuit to the right. It works fairly well for the S6400 series sensors when measuring voltage with a multimeter or oscilloscope. But for the S9702, the responsiveness was not as great and so higher resistor values were needed to amplify the signal. However, at such high resistances, it was over the input impedance of my measuring devices, so my measurements were not valid. And even if the measuring devices had a higher input impedance, the input pin of the micro controller only has a 2.5K ohm input impedance.


An interesting find from this attempt was that for sensing ambient room light, the resistor values needed to be higher for green, and even higher for blue in order to get the full voltage swing of response.
[[Image:ProgStiffJointL3.jpg|right|Spring stretch as a function of theta and L2|thumb|450px]]
[[Image:E_Puck_Color_Sensing_Circuit_2.gif|left|Circuit diagram for attempt 2.|thumb|400px]]
[[Image:E_Puck_Color_Sensing_Circuit_3.gif|right|Circuit diagram for attempt 3.|thumb|400px]]
===Attempt 2===
To combat the high impedance of the output, I tried to use an op-amp as a voltage follower. However, I got some strange periodic behavior in the voltage output and also some strange voltage off-set. It would read from -0.5V-ish to 2.7V-ish, a -0.5 off-set. Other more complicated circuitry were also recommended and tried by Prof. Lynch and Prof. Peshkin, but none really worked very well for me.


===Attempt 3===
[[Image:ProgStiffJointTorque.jpg|right|Torque produced by the spring as a function of L2|thumb|450px]]
This was the third and final attempted circuitry. I used a simple transistor to give the output roughly a 200x current gain, which allowed me to use a much smaller resistor to lower the output impedance. Although I did not look into this in great detail, it seems like there was a huge variance in the amount of current gain between individual transistors. Furthermore, different resistor values work better under different light conditions. So a trim-pot might be a better design if you plan on reproducing this circuit.<br><br>


==Code==
The rotational stiffness of a joint is defined as the torque required to cause a given angular rotation.
[http://hades.mech.northwestern.edu/wiki/images/0/0e/E-puck_color_sensing_project_source.rar Full source code]


==Code Overview==
===Overview===
The code listed here will allow the e-Puck to sync up with the MATLAB program, calibrate the sensor, start the lawn mowing sweep, and update position/heading coordinates. Sensor readings along with the location are taken every 200ms and sent back to the PC only when the e-Puck is going in a straight line. Much of the code was programmed in the same fashion as the standard library of e-Puck codes and NUtest.


===Code for e-Puck===
===Code for e-Puck===
This code borrows many functions from the standard e-Puck library and other added functions by Prof. Lynch in the NUtest.c version of the e-Puck code.


<pre>
===Code for MATLAB===
// main.c for RedScan project
// James Yeung
// 6/1/2008
//
// This code is for the e-puck to be used in conjunction with RedScan.m in MATLAB. It will control the e-Puck to set up the bluetooth
// connection with PC, calibrate the sensor, perform the lawn mowing pattern, send sensor readings to PC, and allow user update of
// position/heading coordinates.


#include <p30f6014A.h> // contains register definitions, etc., for the dsPIC
==Results==


#include <string.h> // string manipulation
Based upon the diagram at right, the following variables are defined for these equations:
#include <ctype.h> // contains the "toupper" command to convert lower to uppercase
* L1 = The distance from the pivot point to the "heel" insertion point of the linear spring on the base plate
#include <stdio.h> // standard I/O routines
* L2 = The distance from the pivot point to the "shank" insertion point of the linear spring on the rotating element
#include <math.h> // math functions like sqrt, cos, etc.
* L3 = The distance between the two insertion points
* h = The height of the insertion point on the rotating member for a given angle and L2
* a = The horizontal component of the position of the insertion point location on the rotating member for a given angle and L2
* theta = The angle of the rotating member with respect to the vertical (perpendicular to the base plate)
* beta = The angle of the spring tension with respect to the rotating member


#include <a_d/advance_ad_scan/e_ad_conv.h> // reads the A/D channelsp
Below is the bottom line for rotational stiffness. For more detailed information, see the separate [[Rotational Stiffness]] page.
#include <a_d/advance_ad_scan/e_acc.h> // reads the 3 accelerometer channels (using e_ad_conv)
#include <motor_led/e_epuck_ports.h> // gives mnemonic names to pins, defines simple asm functions
#include <motor_led/e_init_port.h> // initializes the port values and whether input or output
#include <motor_led/advance_one_timer/e_led.h> // uses the LED's with interrupts, to allow blinking, etc.
#include <motor_led/advance_one_timer/e_motors_NU.h> // interrupt-driven motor routines (step every x millisecs, e.g.)
#include <motor_led/advance_one_timer/e_agenda_NU.h> // manages "agenda" (the interrupt routines)
#include <uart/e_uart_char.h> // uses the uarts for comm (in this case, bluetooth with PC)


#define uart_send_static_text(msg) do { e_send_uart1_char(msg,sizeof(msg)-1); while(e_uart1_sending()); } while(0)
There are three major aspects to consider for rotational stiffness:
#define uart_send_text(msg) do { e_send_uart1_char(msg,strlen(msg)); while(e_uart1_sending()); } while(0)
#define SENSOR_OFFSET 4.5


static char buffer[52*39*2+3+80];
First, a linear spring requires a constant incremental force to achieve a given incremental change in the spring length. This is described by Hook's Law (shown below) where F is the force applied to the spring, x is the change in length of the spring (in this case, it will only be extension), and k is the stiffness of the linear spring.
int i;
char c='z';
float x,y,theta,dist,heading,diff,x_target,y_target,theta_target,speed,j;


// return the angle limited to [-PI, PI]; PI defined in e_motors_NU.h
<math>F = k * x</math>
float anglelimit(float ang) {
while (ang<-PI) ang += 2.0*PI;
while (ang>PI) ang -= 2.0*PI;
return ang;
}


void go_to(float x_target, float y_target, float theta_target){
Due to this relationship, the amount of extension of the spring will determine the tension force applied to the sliding insertion point on the rotating member. Therefore as the amount of spring extension increases, the amount of rotational stiffness increases.
theta_target = anglelimit(theta_target*DEG2RAD);
e_get_configuration(&x,&y,&theta);
dist = sqrt((x-x_target)*(x-x_target)+(y-y_target)*(y-y_target));
if (dist>0.1) { // don't do the first rotate and translate if goal is too close
heading = atan2(y_target-y,x_target-x);
diff = anglelimit(heading-theta);
if (diff<(-0.5*PI)) {
dist = -dist;
diff = diff+PI;
}
else if (diff>(0.5*PI)) {
dist = -dist;
diff = diff-PI;
}
speed = e_rotate(diff,2.0); // rotate at 2 radians/sec
while((e_get_goal_active_left()!=0) || (e_get_goal_active_right()!=0));
theta = theta + diff;
speed = e_translate(dist,4.0); // translate at 4 cm/sec (max around 12.9 cm/sec)
while((e_get_goal_active_left()!=0) || (e_get_goal_active_right()!=0));
}
diff = anglelimit(theta_target - theta);
if (abs(diff)>0.02) { // don't do final rotate if already close to desired angle
speed = e_rotate(diff,2.0); // rotate at 2 radians/sec
while((e_get_goal_active_left()!=0) || (e_get_goal_active_right()!=0));
}
}


void read_incoming(void){
Secondly, a torque (T) is the result of a force (F) applied at a distance (d) from the axis of rotation. As the distance from the axis increases, the torque produced by a given tension force increases, therefore the rotational stiffness increases.
int flag=0;
while (e_getchar_uart1(&c)==0 || flag == 0){ // function in uart/e_uart_char.h
if(c>0){
flag = 1;
}
}
buffer[0]=c;
i = 1;
do if (e_getchar_uart1(&c)) // "do" put chars in string while chars available
buffer[i++]=c;
while (c!='z'); // end "do" when char is newline or return
buffer[i++]='\0'; // end the string
}


void wait4confirm(void){
<math>T = F * d</math>
c = 'z';
while (c!='A'){
e_getchar_uart1(&c);
}
c = 'z';
}


void send_reading(void){
Lastly, the angle (beta) between the tension force and the rotating member matters. Only the component of the force which is tangential to the rotation is generating torque. The rest of the force becomes a compressive force in the rotating member, and does not contribute. As the angle between the spring and the vertical member decreases, the rotational stiffness decreases.
e_get_configuration(&x,&y,&theta);
sprintf(buffer,"%fz%fz%dz",x,y,e_get_acc(0));
uart_send_text(buffer);
}


int main(void){
<math>F(tangential) = F(tension) * sin(beta)</math>
e_init_port(); // configure port pins
e_start_agendas_processing(); // start the motor interrupt service routines
e_init_motors();
e_init_uart1();
e_init_ad_scan(ALL_ADC);
e_set_agenda_cycle(send_reading, 200); // setup send_reading agenda in interrupt


uart_send_static_text("Az"); // send confirmation for communication link
This all condenses into the following torque formula in terms of the variables from the conceptual diagram:


wait4confirm();
T = k * ( (L2*cos(theta))^2 + (L1+L2*sin(theta))^2 )^1/2 * sin(beta) * L2
e_acc_calibr(); // calibrate sensor for black


wait4confirm();
<math>beta = pi/2 - theta - arctan(h/a)</math>
sprintf(buffer,"%dz",e_get_acc(0));
uart_send_text(buffer); // calibrate sensor for white


wait4confirm();
===MATLAB Simulation===
e_set_configuration(0.0,0.0,0.0); // calibrate e-puck's location/heading to (0,0,0)


e_activate_agenda(send_reading, 200); // start send_reading agenda in interrupt
Simulations of the above theory in MATLAB produced the following results.
e_pause_agenda(send_reading); // pause send_reading agenda in interrupt


j=0;
Over the range of motion of this device (-20 degrees to +30 degrees) the spring stretch is pretty close to linear. This suggests that there will be limited change of joint rotational stiffness due to non-linear spring stretch.
for(i=0;i<25;i++){
e_restart_agenda(send_reading); // start send_reading agenda in interrupt
go_to(50.0,j+0.0,0.0);
e_pause_agenda(send_reading); // pause send_reading agenda in interrupt
uart_send_static_text("Az"); // send confirmation for image update
go_to(50.0,j+1.0,0.0);
e_restart_agenda(send_reading);
go_to(0.0,j+1.0,0.0);
e_pause_agenda(send_reading);
uart_send_static_text("Az"); // send confirmation for image update
go_to(0.0,j+2.0,0.0);


e_get_configuration(&x,&y,&theta); // send pos/heading info
As the L2/L1 ratio increases, there seems to be a plateau for L2/L1 > 2. This suggests that the torque gain due to spring tension in this region would be minimal.
sprintf(buffer,"%fz%fz%fz",x,y,theta*RAD2DEG);
uart_send_text(buffer);
read_incoming(); // wait for pos/heading update
sscanf(buffer,"(%f,%f,%f)z",&x,&y,&theta); // update pos/heading info
e_set_configuration(x,y,theta*DEG2RAD);
j=j+2.0;
}
return 1;
}
</pre>


===Code for MATLAB===
The peak torque produced by the spring seems to be at a ratio of L2/L1 = 1. Above this point, the angle beta is too small, and not enough of the spring tension force is tangential to the rotation, so the torque actually decreases. Below this ratio, the amount of spring stretch is decreasing, resulting in reduced torque. The ideal region to be working within is the region:


====RedScan.m====
<math>0 < L2/L1 < 1</math>
The main script that starts the connection and interface.
<pre>
% RedScan.m James Yeung 6/1/2008
% This program is designed to communicate to an e-puck via blutooth using RS232 protocol.
% red_scan.hex will have to be on the e-puck for this to work properly.
%
% The main loop of this program waits for a character input from the user,
% upon which it transmits the ascii value and waits for data to be written.


output_file = fopen('output.txt','w');
===MATLAB Code===


image = zeros(500, 500, 3);
The matlab code used to produce the plots used in this section and the [[Rotational Stiffness]] page is linked below.


s = serial('COM6','BAUD',19200); % Create serial object (PORT Dependent)
[[media:stiffnesstheory.m|stiffnesstheory.m]]
fopen(s); % Open the serial port for r/w


inNum(64) = 0;
The code is heavily commented and is intended to be used either stand-alone or in conjunction with the [[Rotational Stiffness]] page. The plots produced are internally formatted and ready for viewing upon running the code. The code computes and plots the location of the sliding insertion point on the rotating member relative to the stationary insertion point on the base. It also computes and plots the amount of spring stretch experienced for a given combination of rotation and insertion distance ratios, assuming that the spring is given slack so that the neutral angle is consistent at -20 degrees (or -pi/9 radians). Finally, it computes the amount of tension force actually being used to produce torque as well as the respective moment arm, and from that plots the amount of torque required to achieve the rotation at different insertion distance ratios.
outChar = 'a';
ii = 0;


input('Restart e-puck and press enter.');
True rotational stiffness would be the incremental change in torque needed to produce a small change in angle, however the code does not preform this computation. From the plots, over the range of angles used for this application the torque vs angle curves are close to linear, and therefore we assumed a linear relationship for the sake of simplicity and ignored the differential stiffness. If the joint were to incorporate a larger range of motion or were to be required to maintain a specific stiffness, computing the actual stiffnesses over the range of angles and insertion distance ratios would be necessary, however this project did not require that.


ReadNum;
==Mechanical Design==
if(inNum(1) == 65)
[[Image:Mechanical Design Labels.JPG|right|Photo of Mechanical Components|thumb|300px]]
fprintf('Communication link ready.\n');
else
fprintf('Error: Restart e-puck and MATLAB program.\n');
end


input('Place e-puck in dark spot for calibration. Press enter when ready.');
The Mechanical Design consists of six main parts, the base, track/slider, threaded rod, spring, and two motors. The track, base, and spring are representative of the three main parts of an ankle joint: the foot, leg, and calf muscle respectively. We have specified 10 different stiffness levels based on the height of the slider. The slider sits on the threaded rod and moves up and down the track as the motor spins the rod to adjust its position. One motor is used to turn the rod, which controls the height of the slider. The other motor is used to tighten the spring after the height of the slider has been set.
fprintf(s,'%c','A');
input('Place e-puck in bright spot for calibration. Press enter when ready.');
fprintf(s,'%c','A');
ReadNum;
max_val = temp;
fprintf('Max value: %d',temp);
fprintf('\n');
input('Place e-puck in location/heading (0,0,0). Press enter when ready.');
imshow(image);
fprintf(s,'%c','A');


for iii = 1:25
===Structural Components===
for jjj = 1:2
ReadNum;
while(inNum(1) ~= 65)
x = round(temp*10);
ReadNum;
y = round(temp*10);
ReadNum;
image(500-y+1:500-y+10,x+1:x+5,1) = temp/max_val;
ReadNum;
end
inNum(1) = 66;
imshow(image);
end
UpdatePos;
end


outChar = input('Type "A" to get value, "Q" to quit: ','s');
*Base - The base is connected to the track by the pillow blocks and rod. It is also connected to the spring. In the ankle joint, the base would be equivalent to the foot. The base is made out of aluminum.
while(outChar ~= 'Q')
fprintf(s,'%c',outChar);
ReadNum;
fprintf('Current value: %d\n',temp);
outChar = input('Type "A" to get value, "Q" to quit: ','s');
end


*Track - The track is connected to the base through the use of pillow blocks and a rod. The track allows the slider to stay in place and be moved to different heights based on the desired stiffness. The track is an extruded part made out of aluminum alloy.


% myChar = 'a';
*Spring - Depending on the position of the slider, the spring extends different amounts for the same rotation, thus providing different rotational stiffness to the joint. The spring is connected to a cable, allowing it to maintain unstretched at the set point for different slider heights.
% prompt = 'Enter a character (q to exit): ';
%
% while (myChar ~= 'q') % While user hasn't typed 'q'
% fprintf(s, '%s', myChar(1)) % Write first char of user input to serial port
% fprintf(fscanf(s)) % Read Data back from PIC
% myChar = input(prompt, 's'); % Get user input
% end


fclose(output_file);
*Slider - The slider sits in the track and has a tapped hole down the middle allowing the threaded rod to sit inside it. It is moved up and down the track depending on the way the threaded rod spins. It also has another hole allowing the cable to pass through and connect to the spring. The position of the slider determines the force the spring applies to the system. The slider is a milled piece of aluminum.
fprintf('Closing communication link...\n');

fclose(s); % Close the serial port
*Threaded Rod - The threaded rod sits inside the slider and causes the slider to change heights depending on the direction of rotation. The threaded rod is rotated by the Faulhaber Motor. It is a 3/8 inch threaded rod.
fprintf('Communication link closed.\n');

fprintf('Deleting communication link...\n');
*Faulhaber Motor w/ Encoder - The Faulhaber motor is connected to the threaded rod by a set screw. As the motor turns, so does the rod. The encoder keeps track of the number of rotations, allowing us to control the position of the slider.
delete(s); % Delete the serial object

fprintf('Communication link deleted.\n');
*Pillow Blocks - The pillow blocks elevate the track enough to allow it to rotate freely.
fprintf('Program terminated.\n');

</pre>
*Rod - The rod is made of brass. It connects the track to the pillow blocks and allows rotation of the joint.

*Pulley and Cable - The pulley is driven by a motor with a worm gearbox to prevent back driving. The cable goes through a hole in the slider and connects to the spring.

*Tamiya Worm Gearbox Motor - The gearbox motor controls the pulley and cable and serves to tighten the spring once the slider is in the appropriate height.

*Stop Supports - The stop supports serve as a resting point for the track when the spring is not in tension or when the slider is in the lowest position.

===How It Works===

Once a change in the desired stiffness is detected, the Worm Gearbox Motor turns the pulley to provide a little slack to the cable. Then the Faulhaber motor would spin the rod in the direction that will move the slider to the desired position that corresponds to the desired stiffness. Once the slider gets to the desired position, the Worm Gearbox Motor turns the pulley until all slack in the cable has been removed. Now, the new desired stiffness level has been set.

==Electrical Design==

[[Image:ProgStiffJointCircuit.jpg|right|Photo of circuit board|thumb|300px]]

===Component List===
<table border=1>
<tr><th>Part</th><th>Part No. (Spec Sheet)</th><th>Qty</th><th>Vendor (Where to Buy)</th><th>Unit Price</th></tr>
<tr><td>Microchip 8-bit PIC Microcontroller</td><td>[http://ww1.microchip.com/downloads/en/DeviceDoc/39631a.pdf PIC18F4520]</td><td>1</td><td>[http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=1335&dDocName=en010297 Microchip]</td><td>~$4.50</td></tr>
<tr><td>Faulhaber Motor w/ Encoder</td><td>[http://hades.mech.northwestern.edu/wiki/index.php/Actuators_Available_in_the_Mechatronics_Lab#Faulhaber_1524E006S_motor_with_141:1_gearhead_and_HES164A_magnetic_quadrature_encoder 1524E006S123]</td><td>1</td><td>[http://www.goldmine-elec-products.com/prodinfo.asp?number=G16279 Electronic Goldmine]</td><td>$7.95</td></tr>
<tr><td>Tamiya Worm Gearbox High Efficiency Kit</td><td>[http://www.tamiyausa.com/product/item.php?product-id=72004 TAM72004]</td><td>1</td><td>[http://www.hobbylinc.com/htm/tam/tam72004.htm Hobbylinc]</td><td>$9.09</td></tr>
<tr><td>H-Bridge Chip</td><td>[http://www.ortodoxism.ro/datasheets/stmicroelectronics/1328.pdf L293B]</td><td>1</td><td>[http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail?name=497-1389-5-ND Digi-Key]</td><td>$4.32</td></tr>
<tr><td>Diodes</td><td>[http://www.onsemi.com/pub_link/Collateral/1N4001-D.PDF 1N4001RLOSCT-ND]</td><td>8</td><td>[http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail?name=1N4001RLOSCT-ND Digi-Key]</td><td>$0.23</td></tr>
<tr><td>7-Segment Display</td><td>[http://media.digikey.com/pdf/Data%20Sheets/Lite-On%20PDFs/LTD-4708JS.pdf LTD-4708JS]</td><td>1</td><td>[http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail?name=160-1535-5-ND Digi-Key]</td><td>$1.20</td></tr>
<tr><td>7-Segment Display Chip</td><td>[http://www.standardics.nxp.com/products/hef/datasheet/hef4543b.pdf HEF4543B]</td><td>1</td><td>[http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail?name=568-3138-5-ND Digi-Key]</td><td>$0.50</td></tr>
<tr><td>100K Pot Linear Taper w/ Dial</td><td>Jameco 29102</td><td>1</td><td>Discontinued</td><td>N/A</td></tr>
<tr><td>Lever/Limit Switch</td><td>[http://www.components.omron.com/components/web/pdflib.nsf/0/5673FC48B47CDC9C85257201007DD56F/$file/D2F_0607.pdf SW152-ND]</td><td>1</td><td>[http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail?name=SW152-ND Digi-Key]</td><td>$1.10</td></tr>
<tr><td>Slider Switch</td><td>Jameco 135378 (Variety Pack)</td><td>1</td><td>[http://www.jameco.com/webapp/wcs/stores/servlet/ProductDisplay?langId=-1&storeId=10001&catalogId=10001&pa=135378&productId=135378 Jameco]</td><td>~$0.14</td></tr>
</table>

===Circuit Diagram===

Here's a detailed drawing of the circuit. The interface with the PIC microcontroller has been ignored but more information about how to put one together is available [http://hades.mech.northwestern.edu/wiki/index.php/Main_Page#PIC_18F4520_prototyping_board here].
<br>
[[Image:SteelToeCircuit.gif|Detailed drawing of circuit|800px]] <br>

==Code==
[http://hades.mech.northwestern.edu/wiki/images/6/6e/SteelToeCode.c Full source code]


===Overview Comments===
====ReadNum.m====
A sub routine to read from incoming RS232 stream. Stores individual incoming characters in "inNum(ii)" and the final number in "temp".
<pre>
<pre>
ii = 1;
/*////////////////////////////////////////////////////////////////////////////////////////////////////////////
inNum(ii) = fread(s,1);
stiffness_control.c, j.yeung, a.care, e.nickel 2008-01-31
while(inNum(ii) ~= 122)
ii = ii + 1;
inNum(ii) = fread(s,1);
end


ii = 1;
This code is to be used with the programmable stiffness joint.
while(inNum(ii) ~= 0)
buffer(ii) = cast(inNum(ii),'char');
ii = ii + 1;
end


temp = sscanf(buffer,'%f',1);
OVERVIEW
A reset routine is run when the program starts that will run the Fauhaber motor until the slider hits the
switch. The reset routine will then reset the encoder count to 0. Then the program polls the analog input
to see what stiffness level the knob is turned to. If the desired stiffness level is different from the
current stiffness level, the program will move the slider to the desired level. The program also controls
the worm drive motor passively as the slider moves to ensure that there is enough slack for the slider to
move and also to tighten up the slack after the slider has reached its position.

NOTES
RD0 - binary output for 7-segment display
RD1 - binary output for 7-segment display
RD2 - binary output for 7-segment display
RD3 - binary output for 7-segment display
RD6 - digital output for encoder miscounts
RD7 - digital output for calibration button
RC0 - digital input for calibration button
RC1 - digital output for PWM of rod motor (low = down, high = up)
RC2 - CCP1 output for PWM of rod motor
RB3 - CCP2 output for PWM of cable motor
RB4 - digital output for PWM of cable motor (low = in, high = out)
RA0 - analog input from dial
RA3 - channel B input from rod motor encoder
RA4 - channel A input from rod motor encoder
/////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
</pre>
</pre>


===PIC Initialization===
====UpdatePos.m====
A sub routine to ask user to manually input position/heading coordinates to update the e-Puck.
<pre>
<pre>
fprintf('e-puck thinks its position/heading is (');
//PIC Initialization///////////////////////////
ReadNum;
#include <18f4520.h> // command to include the header for the PIC.
fprintf('%f,',temp);
#DEVICE ADC=10 // set ADC (analog input) to 10 bit accuracy
ReadNum;
#fuses HS, NOLVP, NOWDT, NOPROTECT, CCP2B3 // some standard fuses, CCP2B3 moves CCP2 to output at RB3
fprintf('%f,',temp);
#use delay(clock=20000000) // 20 MHz crystal on PCB
ReadNum;
///////////////////////////////////////////////
fprintf('%f)\n',temp);
outChar = input('Enter real position/heading (x,y,theta): ','s');
fprintf(s,'%c',outChar);
fprintf(s,'%c','z');
</pre>
</pre>


===Encoder Control===
====ErrorPlot.m====
A routine that is called at the end of the program to plot the error between captured image and theoretical image.
<pre>
<pre>
real_image = zeros(500,500,3);
//Encoder Control//////////////////////////////
for x = 1:500
signed int32 encoder_count=0; // it is signed so just in case it goes negative, it doesn't overflow.
for y = 1:500
int8 encoder_state=0; // will contain only 4 relevant bits: A & B state last time, and this time
real_image(y,x,1) = 1 - sqrt(abs(250-x)^2+abs(250-y)^2)/300;
//
end
signed int decoder_lookup[16] = {0, -1, +1, 0, +1, 0, 0, -1, -1, 0, 0, +1, 0, +1, -1, 0};
end
signed int32 target[10] = {100, 2750, 5500, 8250, 11000, 13750, 16500, 19250, 22000, 24650};
// decoder_lookup is to determine which way the motor is going
// target is to determine what is the target encoder count for each stiffness level.
//
#INT_TIMER2 // designates that this is the routine to call when timer2 overflows
void Timer2isr() { // see ServoSkeleton for how this ISR can interrupt others
//
encoder_state = ((((encoder_state << 1) + input(PIN_A4)) << 1) + input(PIN_A3)) & 15;
// shift over the old states and record the new states
//
encoder_count += decoder_lookup[encoder_state];
// use decoder_lookup table to determine motor direction and update encoder_count
//
if ( ((encoder_state & 8) >> 3) != ((encoder_state & 2) >> 1)
&& ((encoder_state & 4) >> 2) != ((encoder_state & 1) >> 0)) {
output_high(PIN_D6); // turn on D6 LED if there is a miscount
} //
else{ //
output_low(PIN_D6); // turn off D6 LED if there is no miscount
} //
} //
///////////////////////////////////////////////
</pre>


error_image = zeros(500,500);
===Reset Routine===
for x = 1:500
<pre>
for y = 1:500
//Reset Routine////////////////////////////////
error_image(x,y) = image(y,x,1) - real_image(y,x,1);
void reset(){ // only called at the beginning of program
end
int i=8; //
end
int j=0; //
//
output_low(PIN_C1); // set rod motor direction to down
set_pwm1_duty(78); //
output_high(PIN_B4); // set cable motor direction to reel-out
set_pwm2_duty(0); //
delay_ms(150); // let cable motor reel-out for 150ms
output_low(PIN_B4); // stop cable motor
//
while(input(PIN_C0) == 1){ // keep moving the slider down until the limit switch is closed
if (i==8){ // for every 4 seconds
output_high(PIN_B4); // let cable motor reel-out for 75ms
if(j>40){ // if rod motor has been running for more than 20 seconds
delay_ms(50); // only let cable motor reel-out for 50ms
} //
else{ //
delay_ms(75); //
} //
output_low(PIN_B4); //
i=0; //
} //
delay_ms(500); //
i++; //
j++; //
} //
//
set_pwm1_duty(0); // stop rod motor
output_low(PIN_B4); // set cable motor direction to reel-in
set_pwm2_duty(78); // reel-in for 1500ms
delay_ms(1500); //
set_pwm2_duty(0); // stop cable motor
encoder_count = 50; // reset encoder counter
delay_ms(1000); // wait for 1 second
} //
///////////////////////////////////////////////
</pre>


[x,y]=meshgrid(1:500,1:500);
===Change Stiffness===
surf(x,y,error_image,'EdgeColor','none')
<pre>
//Change Stiffness/////////////////////////////
signed int8 level_old=0; // these are signed to allow for math operations in the function
signed int8 level_new=0; //
//
void change_stiffness(){ // only called when there's a change in desired stiffness level
int i=0; //
//
if(level_new - level_old < 0){ // if desired level is lower than previous level
output_high(PIN_B4); // set cable motor direction to reel-out
set_pwm2_duty(0); // reel-out for 500ms
delay_ms(500); //
set_pwm2_duty(78); // stop cable motor
//
while(encoder_count > target[level_new]){
// until the encoder count reaches the new desired count
if(i == 100){ // for every 5 seconds
output_high(PIN_B4); // set cable motor direction to reel-out
set_pwm2_duty(0); // reel-out for 50ms
delay_ms(50); //
set_pwm2_duty(78); // stop cable motor
i = 0; //
} //
output_low(PIN_C1); // set rod motor direction to down
set_pwm1_duty(78); // run rod motor
delay_ms(50); //
i++; //
} //
set_pwm1_duty(0); // stop rod motor after target has been reached
} //
//
else{ // desired level is higher than previous level
output_high(PIN_B4); // set cable motor direction to reel-out
set_pwm2_duty(0); // reel-out for 100ms
delay_ms(100); //
set_pwm2_duty(78); // stop cable motor
//
while(encoder_count < target[level_new]){
// until the encoder count reaches the new desired count
if(i == 100){ // for every 5 seconds
output_low(PIN_B4); // set cable motor direction to reel-in
set_pwm2_duty(78); // reel-in for 50 ms
delay_ms(50); //
set_pwm2_duty(0); // stop cable motor
i = 0; //
} //
output_high(PIN_C1); // set rod motor direction to up
set_pwm1_duty(0); // run rod motor
delay_ms(50); //
i++; //
} //
set_pwm1_duty(78); // stop rod motor after target has been reached
} //
// after desired level is reached, tighten slack
output_low(PIN_B4); // set cable motor direction to reel-in
set_pwm2_duty(78); // reel-in for 1500ms
delay_ms(1500); //
set_pwm2_duty(0); // stop cable motor
} //
///////////////////////////////////////////////
</pre>
</pre>


===Vref Update===
==Results==
The 3D error plots give you the percentage error for each of the points of the captured image compared to the projected image. Ideally, the graph would be flat with the z-intercept at 0. A negative z value indicates a lower than expected value for that coordinate.
<pre>
//Vref_update//////////////////////////////////
int16 Vref_old=0; // need 16 bit because we chose 10 bit accuracy
int16 Vref_new=0; //
//
void Vref_update(){ // routine to update dial/display and call change_stiffness()
Vref_new = read_adc(); // update dial value
//
if (abs(Vref_new - Vref_old) > 30){ // software schmitt trigger
if (Vref_new < 1023){ //
level_new = 0; //
} //
if (Vref_new < 918){ //
level_new = 1; //
} //
if (Vref_new < 816){ //
level_new = 2; //
} //
if (Vref_new < 714){ //
level_new = 3; //
} //
if (Vref_new < 612){ //
level_new = 4; //
} //
if (Vref_new < 510){ //
level_new = 5; //
} //
if (Vref_new < 408){ //
level_new = 6; //
} //
if (Vref_new < 306){ //
level_new = 7; //
} //
if (Vref_new < 204){ //
level_new = 8; //
} //
if (Vref_new < 102){ //
level_new = 9; //
} //
output_d(level_new); // update value on 7-segment display
if(level_new != level_old){ //
change_stiffness(); // change stiffness to new desired level
level_old = level_new; // update level_old
Vref_old = Vref_new; // update Vref_old
} //
} //
} //
///////////////////////////////////////////////
</pre>


The first trial run covered the entire image. The spikes are a glitch that happens when collecting readings. I'm not exactly sure why there are spikes. I also forgot to capture the reproduced image of the first full trial run. In this run, I simply allowed the e-Puck to perform the full lawn mowing pattern to cover the entire image. Initially, I tried to simply feed the e-puck with updated coordinates, but it didn't seem to work very well because each time it would get more and more off course. So I decided to manually move the e-Puck to the correct each time it asked me to update its position coordinates.
===Main Function===
<pre>
//MAIN/////////////////////////////////////////
void main(){ //
//
setup_timer_2(T2_DIV_BY_4, 77, 16); // 16KHz clock, interrupt every 4*50nS * 4 * (77+1) * 16 = 1.0mS
// third argument means the clock is running 16x faster than the interrupt routine, if any
enable_interrupts(INT_TIMER2); // initialization for ISR
enable_interrupts(GLOBAL); //
//
setup_ccp1(CCP_PWM); // PWM output on CCP1/RC2, pin 17
setup_ccp2(CCP_PWM); // PWM output on CCP2/RB3, pin 36.
//
output_high(PIN_C1); // initialize rod motor
set_pwm1_duty(78); //
//
output_high(PIN_B4); // initialize cable motor
set_pwm2_duty(78); //
//
setup_adc_ports(AN0); // Enable analog inputs; choices run from just AN0, up to AN0_TO_AN11
setup_adc(ADC_CLOCK_INTERNAL); // the range selected has to start with AN0
//
set_adc_channel(0); // there's only one ADC so select which input to connect to it; here pin AN0
delay_us(10); // wait 10uS for ADC to settle to a newly selected input
Vref_old = read_adc(); // now you can read ADC as frequently as you like
//
reset(); // run the reset routine
//
while (TRUE) { //
Vref_update(); // update the dial
if(input(PIN_C0) == 1){ // turns on D7 LED if the limit switch is pressed, only for visual reference
output_high(PIN_D7); //
} //
else{ //
output_low(PIN_D7); //
} //
delay_ms(100); //
} //
} //
///////////////////////////////////////////////
</pre>


[[Image:e-puck_color_01.bmp|left|Side view of the error plot. This view demonstrates the overall accuracy of the captured image. The thinner the better. Here, besides the spikes, the error seems to be within 30%.|thumb|300px]]
==Results and Reflections==
[[Image:e-puck_color_02.bmp|left|Another view of the plot.|thumb|300px]]
[[Image:e-puck_color_03.bmp|left|Top view of the plot.|thumb|300px]]
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
The second trial run demonstrates the e-Puck's ability to adjust to manual movement of the e-Puck. When the e-Puck prompts for an update for its position coordinates, I would move the e-Puck to a dramatically different position and give it the coordinates at which I put it at. As you can see from the plots, the diagonal lines are the new path that the e-Puck chose to take due to that movement.


[[Image:e-puck_color_a1.bmp|left|The reconstructed image. Color was distorted by MATLAB when saving to bmp. During the experiment, the shades of red were much smoother.|thumb|300px]]
===Overall Results===
[[Image:e-puck_color_a2.bmp|left|Top diagonal view of the plot. Because I did not cover the entire image in this run, the majority of the plot is simply the dummy data "0".|thumb|300px]]
[[Image:SteelToeResultsPic.JPG|right|thumb|350px]]
[[Image:e-puck_color_a3.bmp|left|From the side, we can see that for the vertical path that the e-Puck took, the error were within 20%. For the diagonal paths, we can't see from this angle.|thumb|300px]]

Overall, our project was a great success. There were things that could have been done differently and can be improved, but we managed to achieve what we have set out to do, which was to make a device that models a joint and have the ability to change its rotational stiffness. We hope that "Steel Toe" will aid researchers in creating better prosthetic legs and joints for people in need.

In terms of obtaining some quantitative results, by testing to see how much horizontal force was needed to pull the shank to 90 degrees, we found that "Steel Toe" was able to achieve a factor of about 5 in the range of stiffnesses. Here's a table of our test results.
<br>
<br>
[[Image:e-puck_color_a4.bmp|left|A top view of the plot for the second run.|thumb|350px]]
<table border=1>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<tr><th>Stiffness Level</th><th>Force Needed to Get to 90 Degrees</th></tr>
===Possible Future Improvements===
<tr><td align=center>0</td><td align=center>2.943 N</td></tr>
* To allow the user to manually input a path for the e-Puck to explore.
<tr><td align=center>1</td><td align=center>3.434 N</td></tr>
<tr><td align=center>2</td><td align=center>4.415 N</td></tr>
<tr><td align=center>3</td><td align=center>5.886 N</td></tr>
<tr><td align=center>4</td><td align=center>7.358 N</td></tr>
<tr><td align=center>5</td><td align=center>9.810 N</td></tr>
<tr><td align=center>6</td><td align=center>11.282 N</td></tr>
<tr><td align=center>7</td><td align=center>12.263 N</td></tr>
<tr><td align=center>8</td><td align=center>13.734 N</td></tr>
<tr><td align=center>9</td><td align=center>14.715 N</td></tr>
</table>

===Possible Mechanical Improvements===
* Move circuitry to shank.
* Reduce weight of foot plate.
* Use faster motor for spinning the rod (or gear up current motor) to increase adjustment speed.

===Possible Electrical/Software Improvements===
* Have the program continuously check for changes in dial. Currently it is only checking when motors are not moving.
* Use a different H-bridge chip to replace existing diodes. L293D instead of L293B.

===Future Directions===

The actual stiffness of the joint is not constant over the range of motion. For this limited range of motion, a linear approximation is valid, however more precise computations accounting for the non-linearity could be of use.

This device could be modified in design to incorporate more powerful motors to drive the slider faster and even possibly during loading to achieve a stiffness which can change during the gait cycle to adapt to conditions or simply better conform to natural gait kinetics.


[[Category:e-puck]]
Following this device with a model incorporating programmable damping would be another step toward differentiating between the elastic stiffness and damping properties of the ankle joint during walking.

Latest revision as of 14:49, 18 August 2009

e-Puck with the Zigbee module add-on board and red color sensor.

Overview

Project by: James Yeung, Electrical Engineering, Class of 2009.
Last updated: June 16, 2008

The goal of this project was to have the e-Puck roll around on the floor under a projected image, take sensor readings with one of the R G or B sensors, send back the position and color data to a listening PC by RS232 protocol via bluetooth, and occasionally receive from the PC new (x,y,theta) data on the actual position of the e-Puck (between receiving this data, the robot keeps track of its position by dead reckoning; upon receiving the data, the robot resets its estimated position to the new data). The robot had to roll around to cover the whole projected image (which is unchanging), and at the end of the run, the host PC had to display the reconstructed image and plot the error from the actual image. On the host PC side, MATLAB was used as an interface to talk to the e-Puck and generate the appropriate graphs.

Circuitry For Color Sensor

The photo diodes that I worked with were from Hamamatsu. These were the ones that were considered.

Circuit diagram for attempt 1.
  • S9702 - RGB sensor (1mm^2 active area) - ~$9 (min 10)
  • S9032-2 - RGB sensor (2mm^2 active area) - ~$15 (min 5)
  • S6430-01 - Red color sensor (4mm^2 active area) - ~$11 (min 10)
  • S6429-01 - Green color sensor (4mm^2 active area) - ~$11 (min 10)
  • S6428-01 - Blue color sensor (4mm^2 active area) - ~$11 (min 10)

I mainly worked with the S9702 and the S6400 series sensors.

Attempt 1

I started by simply hooking the sensors with the circuit to the right. It works fairly well for the S6400 series sensors when measuring voltage with a multimeter or oscilloscope. But for the S9702, the responsiveness was not as great and so higher resistor values were needed to amplify the signal. However, at such high resistances, it was over the input impedance of my measuring devices, so my measurements were not valid. And even if the measuring devices had a higher input impedance, the input pin of the micro controller only has a 2.5K ohm input impedance.

An interesting find from this attempt was that for sensing ambient room light, the resistor values needed to be higher for green, and even higher for blue in order to get the full voltage swing of response.

Circuit diagram for attempt 2.
Circuit diagram for attempt 3.

Attempt 2

To combat the high impedance of the output, I tried to use an op-amp as a voltage follower. However, I got some strange periodic behavior in the voltage output and also some strange voltage off-set. It would read from -0.5V-ish to 2.7V-ish, a -0.5 off-set. Other more complicated circuitry were also recommended and tried by Prof. Lynch and Prof. Peshkin, but none really worked very well for me.

Attempt 3

This was the third and final attempted circuitry. I used a simple transistor to give the output roughly a 200x current gain, which allowed me to use a much smaller resistor to lower the output impedance. Although I did not look into this in great detail, it seems like there was a huge variance in the amount of current gain between individual transistors. Furthermore, different resistor values work better under different light conditions. So a trim-pot might be a better design if you plan on reproducing this circuit.

Code

Full source code

Overview

The code listed here will allow the e-Puck to sync up with the MATLAB program, calibrate the sensor, start the lawn mowing sweep, and update position/heading coordinates. Sensor readings along with the location are taken every 200ms and sent back to the PC only when the e-Puck is going in a straight line. Much of the code was programmed in the same fashion as the standard library of e-Puck codes and NUtest.

Code for e-Puck

This code borrows many functions from the standard e-Puck library and other added functions by Prof. Lynch in the NUtest.c version of the e-Puck code.

// main.c for RedScan project
// James Yeung
// 6/1/2008
//
// This code is for the e-puck to be used in conjunction with RedScan.m in MATLAB.  It will control the e-Puck to set up the bluetooth
// connection with PC, calibrate the sensor, perform the lawn mowing pattern, send sensor readings to PC, and allow user update of
// position/heading coordinates.

#include <p30f6014A.h>  					// contains register definitions, etc., for the dsPIC

#include <string.h>  						// string manipulation
#include <ctype.h>   						// contains the "toupper" command to convert lower to uppercase
#include <stdio.h>   						// standard I/O routines
#include <math.h>    						// math functions like sqrt, cos, etc.

#include <a_d/advance_ad_scan/e_ad_conv.h>  			// reads the A/D channelsp
#include <a_d/advance_ad_scan/e_acc.h>	    			// reads the 3 accelerometer channels (using e_ad_conv)
#include <motor_led/e_epuck_ports.h>        			// gives mnemonic names to pins, defines simple asm functions
#include <motor_led/e_init_port.h>          			// initializes the port values and whether input or output
#include <motor_led/advance_one_timer/e_led.h>  		// uses the LED's with interrupts, to allow blinking, etc.
#include <motor_led/advance_one_timer/e_motors_NU.h>  		// interrupt-driven motor routines (step every x millisecs, e.g.)
#include <motor_led/advance_one_timer/e_agenda_NU.h>  		// manages "agenda" (the interrupt routines)
#include <uart/e_uart_char.h>               			// uses the uarts for comm (in this case, bluetooth with PC)

#define uart_send_static_text(msg) do { e_send_uart1_char(msg,sizeof(msg)-1); while(e_uart1_sending()); } while(0)
#define uart_send_text(msg) do { e_send_uart1_char(msg,strlen(msg)); while(e_uart1_sending()); } while(0)
#define SENSOR_OFFSET 4.5

static char buffer[52*39*2+3+80];
int i;
char c='z';
float x,y,theta,dist,heading,diff,x_target,y_target,theta_target,speed,j;

// return the angle limited to [-PI, PI]; PI defined in e_motors_NU.h
float anglelimit(float ang) {
  while (ang<-PI) ang += 2.0*PI;
  while (ang>PI) ang -= 2.0*PI;
  return ang;
}

void go_to(float x_target, float y_target, float theta_target){
	theta_target = anglelimit(theta_target*DEG2RAD);
	e_get_configuration(&x,&y,&theta);
	dist = sqrt((x-x_target)*(x-x_target)+(y-y_target)*(y-y_target));
	if (dist>0.1) {  // don't do the first rotate and translate if goal is too close
		heading = atan2(y_target-y,x_target-x);
		diff = anglelimit(heading-theta);
		if (diff<(-0.5*PI)) {
			dist = -dist;
			diff = diff+PI;
		}
		else if (diff>(0.5*PI)) {
			dist = -dist;
			diff = diff-PI;
		}
		speed = e_rotate(diff,2.0);     		// rotate at 2 radians/sec
		while((e_get_goal_active_left()!=0) || (e_get_goal_active_right()!=0));
		theta = theta + diff;
		speed = e_translate(dist,4.0);  		// translate at 4 cm/sec (max around 12.9 cm/sec)
		while((e_get_goal_active_left()!=0) || (e_get_goal_active_right()!=0));
	}
	diff = anglelimit(theta_target - theta);
	if (abs(diff)>0.02) {             			// don't do final rotate if already close to desired angle
		speed = e_rotate(diff,2.0);     		// rotate at 2 radians/sec
		while((e_get_goal_active_left()!=0) || (e_get_goal_active_right()!=0));
	}
}

void read_incoming(void){
	int flag=0;
	while (e_getchar_uart1(&c)==0 || flag == 0){     	// function in uart/e_uart_char.h
		if(c>0){
			flag = 1;
		}
	}
	buffer[0]=c;
	i = 1;
	do if (e_getchar_uart1(&c))   				// "do" put chars in string while chars available
		buffer[i++]=c;
	while (c!='z');   					// end "do" when char is newline or return
	buffer[i++]='\0';					// end the string
}

void wait4confirm(void){
	c = 'z';
	while (c!='A'){
		e_getchar_uart1(&c);
	}
	c = 'z';
}

void send_reading(void){
	e_get_configuration(&x,&y,&theta);
	sprintf(buffer,"%fz%fz%dz",x,y,e_get_acc(0));
	uart_send_text(buffer);
}

int main(void){
	e_init_port();						// configure port pins
	e_start_agendas_processing();				// start the motor interrupt service routines
	e_init_motors();
	e_init_uart1();
	e_init_ad_scan(ALL_ADC);
	e_set_agenda_cycle(send_reading, 200);			// setup send_reading agenda in interrupt

	uart_send_static_text("Az");				// send confirmation for communication link

	wait4confirm();
	e_acc_calibr();						// calibrate sensor for black

	wait4confirm();
	sprintf(buffer,"%dz",e_get_acc(0));
	uart_send_text(buffer);					// calibrate sensor for white

	wait4confirm();
	e_set_configuration(0.0,0.0,0.0);			// calibrate e-puck's location/heading to (0,0,0)

	e_activate_agenda(send_reading, 200);			// start send_reading agenda in interrupt
	e_pause_agenda(send_reading);				// pause send_reading agenda in interrupt

	j=0;
	for(i=0;i<25;i++){
		e_restart_agenda(send_reading);			// start send_reading agenda in interrupt
		go_to(50.0,j+0.0,0.0);
		e_pause_agenda(send_reading);			// pause send_reading agenda in interrupt
		uart_send_static_text("Az");			// send confirmation for image update
		go_to(50.0,j+1.0,0.0);
		e_restart_agenda(send_reading);
		go_to(0.0,j+1.0,0.0);
		e_pause_agenda(send_reading);
		uart_send_static_text("Az");			// send confirmation for image update
		go_to(0.0,j+2.0,0.0);

		e_get_configuration(&x,&y,&theta);		// send pos/heading info
		sprintf(buffer,"%fz%fz%fz",x,y,theta*RAD2DEG);
		uart_send_text(buffer);	
	
		read_incoming();				// wait for pos/heading update
	
		sscanf(buffer,"(%f,%f,%f)z",&x,&y,&theta);	// update pos/heading info
		e_set_configuration(x,y,theta*DEG2RAD);
		j=j+2.0;
	}
	return 1;
}

Code for MATLAB

RedScan.m

The main script that starts the connection and interface.

%  RedScan.m  James Yeung 6/1/2008
%  This program is designed to communicate to an e-puck via blutooth using RS232 protocol.
%  red_scan.hex will have to be on the e-puck for this to work properly.
%  
%  The main loop of this program waits for a character input from the user,
%  upon which it transmits the ascii value and waits for data to be written.

output_file = fopen('output.txt','w');

image = zeros(500, 500, 3);

s = serial('COM6','BAUD',19200);            % Create serial object (PORT Dependent)
fopen(s);                                    % Open the serial port for r/w

inNum(64) = 0;
outChar = 'a';
ii = 0;

input('Restart e-puck and press enter.');

ReadNum;
if(inNum(1) == 65)
    fprintf('Communication link ready.\n');
else
    fprintf('Error: Restart e-puck and MATLAB program.\n');
end

input('Place e-puck in dark spot for calibration. Press enter when ready.');
fprintf(s,'%c','A');
input('Place e-puck in bright spot for calibration. Press enter when ready.');
fprintf(s,'%c','A');
ReadNum;
max_val = temp;
fprintf('Max value: %d',temp);
fprintf('\n');
input('Place e-puck in location/heading (0,0,0). Press enter when ready.');
imshow(image);
fprintf(s,'%c','A');

for iii = 1:25
    for jjj = 1:2
        ReadNum;
        while(inNum(1) ~= 65)
            x = round(temp*10);
            ReadNum;
            y = round(temp*10);
            ReadNum;
            image(500-y+1:500-y+10,x+1:x+5,1) = temp/max_val;
            ReadNum;
        end
        inNum(1) = 66;
        imshow(image);
    end
    UpdatePos;
end

outChar = input('Type "A" to get value, "Q" to quit: ','s');
while(outChar ~= 'Q')
    fprintf(s,'%c',outChar);
    ReadNum;
    fprintf('Current value: %d\n',temp);
    outChar = input('Type "A" to get value, "Q" to quit: ','s');
end


% myChar = 'a';                               
% prompt = 'Enter a character (q to exit): '; 
% 
% while (myChar ~= 'q')                       % While user hasn't typed 'q'
%     fprintf(s, '%s', myChar(1))             % Write first char of user input to serial port
%     fprintf(fscanf(s))                      % Read Data back from PIC
%     myChar = input(prompt, 's');            % Get user input
% end

fclose(output_file);
fprintf('Closing communication link...\n');
fclose(s);                                  % Close the serial port
fprintf('Communication link closed.\n');
fprintf('Deleting communication link...\n');
delete(s);                                  % Delete the serial object
fprintf('Communication link deleted.\n');
fprintf('Program terminated.\n');

ReadNum.m

A sub routine to read from incoming RS232 stream. Stores individual incoming characters in "inNum(ii)" and the final number in "temp".

ii = 1;
inNum(ii) = fread(s,1);
while(inNum(ii) ~= 122)
    ii = ii + 1;
    inNum(ii) = fread(s,1);
end

ii = 1;
while(inNum(ii) ~= 0)
    buffer(ii) = cast(inNum(ii),'char');
    ii = ii + 1;
end

temp = sscanf(buffer,'%f',1);

UpdatePos.m

A sub routine to ask user to manually input position/heading coordinates to update the e-Puck.

fprintf('e-puck thinks its position/heading is (');
ReadNum;
fprintf('%f,',temp);
ReadNum;
fprintf('%f,',temp);
ReadNum;
fprintf('%f)\n',temp);
outChar = input('Enter real position/heading (x,y,theta): ','s');
fprintf(s,'%c',outChar);
fprintf(s,'%c','z');

ErrorPlot.m

A routine that is called at the end of the program to plot the error between captured image and theoretical image.

real_image = zeros(500,500,3);
for x = 1:500
    for y = 1:500
        real_image(y,x,1) = 1 - sqrt(abs(250-x)^2+abs(250-y)^2)/300;
    end
end

error_image = zeros(500,500);
for x = 1:500
    for y = 1:500
        error_image(x,y) = image(y,x,1) - real_image(y,x,1);
    end
end

[x,y]=meshgrid(1:500,1:500);
surf(x,y,error_image,'EdgeColor','none')

Results

The 3D error plots give you the percentage error for each of the points of the captured image compared to the projected image. Ideally, the graph would be flat with the z-intercept at 0. A negative z value indicates a lower than expected value for that coordinate.

The first trial run covered the entire image. The spikes are a glitch that happens when collecting readings. I'm not exactly sure why there are spikes. I also forgot to capture the reproduced image of the first full trial run. In this run, I simply allowed the e-Puck to perform the full lawn mowing pattern to cover the entire image. Initially, I tried to simply feed the e-puck with updated coordinates, but it didn't seem to work very well because each time it would get more and more off course. So I decided to manually move the e-Puck to the correct each time it asked me to update its position coordinates.

Side view of the error plot. This view demonstrates the overall accuracy of the captured image. The thinner the better. Here, besides the spikes, the error seems to be within 30%.
Another view of the plot.
Top view of the plot.






















The second trial run demonstrates the e-Puck's ability to adjust to manual movement of the e-Puck. When the e-Puck prompts for an update for its position coordinates, I would move the e-Puck to a dramatically different position and give it the coordinates at which I put it at. As you can see from the plots, the diagonal lines are the new path that the e-Puck chose to take due to that movement.

The reconstructed image. Color was distorted by MATLAB when saving to bmp. During the experiment, the shades of red were much smoother.
Top diagonal view of the plot. Because I did not cover the entire image in this run, the majority of the plot is simply the dummy data "0".
From the side, we can see that for the vertical path that the e-Puck took, the error were within 20%. For the diagonal paths, we can't see from this angle.


A top view of the plot for the second run.





















Possible Future Improvements

  • To allow the user to manually input a path for the e-Puck to explore.