Audino Drawing Robot

by logantmorris03 in Circuits > Arduino

37 Views, 0 Favorites, 0 Comments

Audino Drawing Robot

Pic9.jpg
This project is a recreation of another bot (Drawing Robot for Arduino : 18 Steps (with Pictures) - Instructables). This version takes a hand drawn image and converts it into lines through MATLAB for a two wheeled bot to draw. The modifications to this bot done in order to make the bot use a wide range of different parts and pens.

Supplies

Electronics

  1. 1- Arduino UNO or equivalent- adafruit.com/products/50
  2. Adafruit is now the US Manufacture for Genuine Arduinos! Get them from the source.
  3. 2- Geared 5V Stepper- adafruit.com/products/858
  4. 1- ULN2803 Darlington Driver - adafruit.com/products/970
  5. 1- Half-size breadboard- adafruit.com/products/64
  6. 12- Male-male jumpers- adafruit.com/products/1956
  7. At least two should be 6", the rest can be 3".
  8. 1- Micro servo- adafruit.com/products/169
  9. 1- Male pin header- digikey.com/short/t93cbd
  10. 1- 2 x AA Holder- digikey.com/short/tz5bd1
  11. 1 -3 x AA Holder- digikey.com/short/t5nw1c
  12. 1 -470 uF 25V capacitor - www.digikey.com/product-detail/en/ECA-1EM471/P5155-ND/245014
  13. 1 -SPDT slide switch - www.digikey.com/product-detail/en/EG1218/EG1903-ND/101726
  14. 1- USB micro cable http://amzn.com/B00OC6WR22
  15. 5 - AA Batteries

Hareware:

  1. 2- 1 7/8" ID x 1/8" O-ring- mcmaster.com/#9452K96
  2. 1- Caster 5/8" bearing- mcmaster.com/#96455k58/=yskbki

Install Caster

Pic1.jpg

The caster can be pressed in by hand or by preheating the bracket for an easy fit. Then, screw in the caster holder to the plate.

Optional: Use washers to support plastic

Install Stepper Brackets

Pic2.jpg
Pic3.jpg

Using the same screws, install the stepper brackets on either side of the plate. Be careful to not over torque the screws as this part is delicate.

Install Stepper Motors

Pic4.jpg
Pic5.jpg

Using either bolts or screws with backing spacers, install the stepper motors. There is a small recess in the bracket to help align the stepper motors with their holes.

Pen Holder Bracket

Pic6.jpg

Install the pen holder bracket with screws and washers. The "tongue" of the bracket should face away from the protruding holes on the plate. At this point in time, install the rubber O-ring onto the wheels and press the wheels onto the stepper motor shafts.

Audino

Pic7.jpg

Install the Audino onto the protruding holes on the plate using the screws. Depending on the number of screws, be sure to add the backing spacers to prevent sharp screws from sticking out of the bottom of the plate. At this step also adhere the bread board onto the other side of the plate.

Stepper Motor

Pic8.jpg

Install the servo motor onto the bracket. If using an aftermarket servo, use the backing spacers in order to adjust the fit. Then, install the servo motor arm.

Wiring

Pic10.jpg
Screenshot 2025-04-03 180540.jpg

Install the wiring for the bot.

Quote from original post:

"

The microcontroller provides 5 volt signals to the darlington array which in turn, provides VCC to the stepper coils:

  1. Start with pin next to the ground pin on the darlington driver, and install orange, yellow, green, and blue wires in that order (Image 1).
  2. Attach the jumpers to the following arduino pins (Image 2):
  3. orange - Digital pin 4
  4. yellow - Digital pin 5
  5. green - Digital pin 6
  6. blue - Digital pin 7
  7. Back at the darlington, continue jumper for the other stepper in the reverse of the others:
  8. blue, green, yellow, and orange (Image 3).
  9. Attach the jumpers to the following arduino pins (Image 4):
  10. blue - Digital pin 9 (pin 8 used latter for the servo).
  11. green - Digital pin 10
  12. yellow - Digital pin 11
  13. orange - Digital pin 12

"

Matlab

%Jacob Lehrman

%University of Kansas, Dr. Wilson

%Last Updated: 3/27/25

%Desc: Script to load an image, convert it to lineart, and then translate

%that lineart into stepper motor steps (516.096 steps/rev)

%Functions and code to interpret images by Mathworks


clear

clf

clc


fignum = 1;%Value to make updating figures easier


%User-Defined Values

longest = 270;%Length of the longest side of the physical drawing space in mm

%Note: this assumes that your image will fit on the physical space!

indent = 10;%amount of distance the bot should move in and over when starting. Makes a margin on the left and top of the image. Units:mm.

wheelDiameter = 57.9247;%Diameter of the drive wheels in mm.

wheelBase = 108.0516;%Center to Center distance between the drive wheels. Pen should located at half of this distance.

stepperSteps = 516.096;%number of steps in 1 revolution of the stepper motor.


%Code to load the image


pic = imread("DarkstoneTemple1-6-23.png");%Your picture goes here

%pic = imcomplement(pic);%Comment this line out if your image is black lines on a white background


figure(fignum)

imshow(pic)

fignum = fignum + 1;

%Code to binarize and convert the image to paths


[picSegmentsPixel,xLimPixel,yLimPixel] = imageToPixelSegments(pic);


%Plot the segments to visualize whats going on.


figure(fignum)

clf

fignum = fignum + 1;

for ii = 1:length(picSegmentsPixel)

coords = picSegmentsPixel{ii};

plot(coords(:,2),coords(:,1),'-*')

hold on

end

hold off

axis equal ij

axis([0 size(pic,2) 0 size(pic,1)])

title('Paths to draw (pixels)')


%Code to optimize the paths


%Eliminate exceptionally short paths

shortPathLength = 5;

%Five is arbitraily chosen as the number to define a long enough path here to eliminate the shortest paths in our test image.

%You should pick a number that best suits your usecase, or comment this block out entirely.


ii = 1;

while ii <= length(picSegmentsPixel)

if size(picSegmentsPixel{ii},1) <= shortPathLength

picSegmentsPixel(ii) = [];

else

ii = ii + 1;

end

end


%Eliminate redundant points from paths


firstPassSegments = [];%empty array to hold thge segments for the first pass of optimization.


for ii = 1:length(picSegmentsPixel)

%Grab the segment's points

segment = picSegmentsPixel{ii};

%Compute an initial unit vector to compare against

%Note that since we are still computing pixelwise, we don't need to

%normalize our vectors as each pixel is within 1 unit of every other.

startIndex = 1; %Save this for later checking

Point1 = segment(1,:);

Point2 = segment(2,:);

baseVector = Point2 - Point1;

jj = 3;

while jj <= length(segment)

%code to check if the points can be replaced

%Compute the unit vector to the next point

Point1 = segment(jj-1,:);

Point2 = segment(jj,:);

vector = Point2 - Point1;

%If its in-line, it can be replaced

if (vector == baseVector) & (jj < length(segment))

jj = jj + 1;

elseif jj == (2 + startIndex) & (jj < length(segment))%if the part is 2-points long, just update it unless its the end of a segment

jj = 3 + startIndex;%Move jj's starting position up by the number of optimizations that have been done.

startIndex = startIndex + 1;%Increment the Starting Position

Point1 = segment(startIndex,:);%Recompute the base Vector

Point2 = segment(startIndex+1,:);

baseVector = Point2 - Point1;

else

%Remove unneeded points

if jj == length(segment)%Fixes a bug where the end of a segment would have an improperly placed extra point

jj = jj+1;

for k = startIndex:(jj-3)

segment(1+startIndex,:) = [];%Removes points between endpoints of a continuous direction

end

else

for k = startIndex:(jj-3)

segment(1+startIndex,:) = [];%Removes points between endpoints of a continuous direction

end

jj = 3 + startIndex;%Move jj's starting position up by the number of optimizations that have been done.

startIndex = startIndex + 1;%Increment the Starting Position

Point1 = segment(startIndex,:);%Recompute the base Vector

Point2 = segment(startIndex+1,:);

baseVector = Point2 - Point1;

end

end

end

firstPassSegments{ii} = segment;

end


%First Straight Line approx. Optimization pass. Checks for larger patterns to see

%if any of them can be reasonably replaced with straight lines

secondPassSegments = straightLineOptUnweighted(firstPassSegments,1.5,4);


%Redraw for testing

figure(fignum)

fignum = fignum + 1;

for ii = 1:length(secondPassSegments)

coords = secondPassSegments{ii};

plot(coords(:,2),coords(:,1),'-*')

hold on

end

hold off

axis equal ij

axis([0 size(pic,2) 0 size(pic,1)])

title('4 Point')



%Second Straight Line approx. Optimization pass. Refining previous


thirdPassSegments = straightLineOptUnweighted(secondPassSegments,1,3);

thirdPassSegments = straightLineOptUnweighted(thirdPassSegments,1,3);


%Redraw for testing

figure(fignum)

fignum = fignum + 1;

for ii = 1:length(thirdPassSegments)

coords = thirdPassSegments{ii};

plot(coords(:,2),coords(:,1),'-*')

hold on

end

hold off

axis equal ij

axis([0 size(pic,2) 0 size(pic,1)])

title('3 Point')


%Third Straight Line approx. Optimization pass. Refining previous


fourthPassSegments = straightLineOptUnweighted(thirdPassSegments,2,2);

for ii = 2:3

fourthPassSegments = straightLineOptUnweighted(fourthPassSegments,1,2);

end


%Redraw for testing

figure(fignum)

fignum = fignum + 1;

for ii = 1:length(fourthPassSegments)

coords = fourthPassSegments{ii};

plot(coords(:,2),coords(:,1),'-*')

hold on

%pause(.5)

end

hold off

axis equal ij

axis([0 size(pic,2) 0 size(pic,1)])

title('2 Point')


%Code to convert the paths into stepper motor instructions


%Step 1: make it so the bot goes to the next closest path each time. This is

%because angle errors will make for nasty pics


for ii = 1:length(fourthPassSegments)-1

Path1 = fourthPassSegments{ii};

dist = zeros(length(fourthPassSegments)-ii,1);%Preallocating

for jj = 1:length(fourthPassSegments)-ii

Path2 = fourthPassSegments{jj+ii};

dist(jj) = sqrt(sum((Path1(end,:)-Path2(1,:)).^2));

end

[~,Index] = min(dist);%find the index of the minimum distance

Path3 = fourthPassSegments{ii+1};%Temporary for moving values

Path4 = fourthPassSegments{Index+ii};

fourthPassSegments{ii+1} = [];%since Matlab does not support moving lists like variables, delete them first then reassign

fourthPassSegments{Index+ii} = [];

fourthPassSegments{ii+1} = Path4;%Move the closest path to be next

fourthPassSegments{Index+ii} = Path3;%Put the previous closest where the previous path was.

end


%Step 2: convert pixels to metric distances


longestPixel = max(xLimPixel(1,2), yLimPixel(1,2));

mmPerPixel = longest/longestPixel;%Obtain ratio of mm/pixel

for ii = 1:length(fourthPassSegments)

fourthPassSegments{ii} = (fourthPassSegments{ii}.*mmPerPixel)+indent;%Actually convert the paths

end


%Step 3: compute how far a 'step' is for the bot, and how much turning will

%occur


stepSize = (wheelDiameter*pi)/stepperSteps;%step size in mm

stepAngle = (2*stepSize)/(wheelBase);%angle turned when moving a single step tankwise. Measured in radians


%Step 4: Compute vector Paths to each point, and if the pen should be up or

%down. Save this as an array so it can be exported to arduino easily.


botPos = [0; 0];%Assume bot starts at 0,0 on grind.

botStartingVector = [1; 0];%assume bot is facing to the right. Has to be a collum due to math.

botVector = botStartingVector;

rotate = @(angle) [cos(angle) -sin(angle); sin(angle) cos(angle)];%Vector Rotation Matrix

angleBetween = @(v1,v2) acos(sum(v1.*v2)/(sqrt(sum(v1.^2))*sqrt(sum(v2.^2))));%Function to compute angle between vectors

xAxis = [1;0];%Vector for x-axis. Angles will be measured to here.

motorSteps = [];%Empty array to hold the motor steps


%Note: due to how the image is processed, it is mirrored over the x axis for

%some reason. this means that all y-coordinates will have to be inverted to

%plot correctly.

%motorSteps are saved in the order Pen, angle, forward.

%Angle steps should follow RHR, that is CCW rotation is Positive

angleBuffer = 0;%Buffers for round-offs of motorsteps.

lengthBuffer = 0;



for ii = 1:length(fourthPassSegments)

%Lead with calc from end to start of next

segment = fourthPassSegments{ii}';

holding = segment(1,:);%Swapping rows to make it [X; Y]

segment(1,:) = segment(2,:);

segment(2,:) = -holding;


start = segment(:,1)-botPos;%vector pointing to start of the new segment

angleToAxis = angleBetween(botVector,xAxis);%This should compute the angle between the bot and a hypothetical x axis.


if botVector(2) > 0

angleToAxis = -angleToAxis;

end

botVector = rotate(angleToAxis)*botVector;%Rotate all vectors to the imaginary horizontal axis.

start = rotate(angleToAxis)*start;

rotationAngle = angleBetween(botVector,start);%Computes the angle the bot needs to turn to get to the next point

angleSteps = rotationAngle/stepAngle;%Computes the number of rotation steps needed to get close enough to the angle.


if start(2) < 0

angleSteps = angleSteps*-1;

end


lengthSteps = sqrt(sum(start.^2))/stepSize;%Computes the number of steps needed to get to the next position.

botVector = segment(:,1)-botPos;%Update the direction the bot is facing.

botPos = segment(:,1);%Update the bots position to the next point


angleBuffer = angleBuffer + angleSteps - round(angleSteps);%Saves the total distance rounded off.

lengthBuffer = lengthBuffer + lengthSteps - round(lengthSteps);


if abs(round(angleBuffer)) >= 1

angleSteps = round(angleSteps) + round(angleBuffer);

angleBuffer = angleBuffer - round(angleBuffer);

else

angleSteps = round(angleSteps);

end


if abs(round(lengthBuffer)) >= 1

lengthSteps = round(lengthSteps) + round(lengthBuffer);

lengthBuffer = lengthBuffer - round(lengthBuffer);

else

lengthSteps = round(lengthSteps);

end


%Save Data

motorSteps(end+1) = 0;%Set value to lift the pen up

motorSteps(end+1) = angleSteps;%Save number of angle steps to rotate

motorSteps(end+1) = lengthSteps;%Save distance to travel after rotating




for jj = 2:length(segment)

%Now to trace the segment

start = segment(:,jj)-botPos;%vector pointing to next point

angleToAxis = angleBetween(botVector,xAxis);%This should compute the angle between the bot and a hypothetical x axis.


if botVector(2) > 0

angleToAxis = -angleToAxis;

end


botVector = rotate(angleToAxis)*botVector;%Rotate all vectors relative to the imaginary horizontal axis.

start = rotate(angleToAxis)*start;

rotationAngle = angleBetween(botVector,start);%Computes the angle the bot needs to turn to get to the next point

angleSteps = rotationAngle/stepAngle;%Computes the number of rotation steps needed to get close enough to the angle.


if start(2) < 0

angleSteps = angleSteps*-1;

end

lengthSteps = sqrt(sum(start.^2))/stepSize;%Computes the number of steps needed to get to the next position.

botVector = segment(:,jj)-botPos;%Update the direction the bot is facing.

botPos = segment(:,jj);%Update the bots position to the next point


%So apparently the round-off errors are big enough to make the image

%unviewable when drawn. time to correct them.


angleBuffer = angleBuffer + angleSteps - round(angleSteps);%Saves the total distance rounded off.

lengthBuffer = lengthBuffer + lengthSteps - round(lengthSteps);


if abs(round(angleBuffer)) >= 1

angleSteps = round(angleSteps) + round(angleBuffer);

angleBuffer = angleBuffer - round(angleBuffer);

else

angleSteps = round(angleSteps);

end


if abs(round(lengthBuffer)) >= 1

lengthSteps = round(lengthSteps) + round(lengthBuffer);

lengthBuffer = lengthBuffer - round(lengthBuffer);

else

lengthSteps = round(lengthSteps);

end


%Save Data

motorSteps(end+1) = 1;%Set value to set the pen down

motorSteps(end+1) = angleSteps;%Save number of angle steps to rotate

motorSteps(end+1) = lengthSteps;%Save distance to travel after rotating

end

end


%Plot the path the bot should follow

figure(fignum)

clf

fignum = fignum + 1;

plottingAngle = 0;

plottingPoint = [0 0];

for ii = 1:3:length(motorSteps)

plottingAngle = plottingAngle + (motorSteps(ii+1)*stepAngle);

ppOld = plottingPoint;%Save the old plotting point

plottingPoint = plottingPoint + ([cos(plottingAngle) sin(plottingAngle)].*stepSize*motorSteps(ii+2));

plotMatrix = [ppOld(2) ppOld(1); plottingPoint(2) plottingPoint(1)];

if motorSteps(ii) == 1

plot(plotMatrix(:,2),plotMatrix(:,1),'-*')

hold on

pause(.05)

end

end

hold off

axis([indent xLimPixel(2)*mmPerPixel+indent -(yLimPixel(2)*mmPerPixel+indent) -indent])

title('Predicted Bot Path')


%Save bot path in text format for copying to Arduino


writematrix(motorSteps)

Matlab 2

function passSegmentsUn = straightLineOptUnweighted(segmentsPix,offLim,optLevel)

%Jacob Lehrman

%Last Updated: 2/23/25

%Desc: Function to optimize paths based on a given number of points

for ii = 1:length(segmentsPix)

%Grab the segment's points

segment = segmentsPix{ii};

if length(segment) > optLevel

startIndex = 1; %Save this for later checking

%Compute an initial unit vector to compare against

unitVector = unitVectorUnweight(segment,startIndex,optLevel);


jj = optLevel+startIndex;%Set the starting position

while jj <= length(segment)

%compute the vector to the next point and its magnitude

vector = segment(jj,:) - segment(startIndex,:);

magVec = sqrt(sum(vector.^2));

%compute the angle between it and the unit vector

%Don't have to have unit vectors magnitude on bottom as it is 1 by definition

theta = acos(sum(vector.*unitVector)/magVec);

%Compute how far away from the unit vector line the point is

offset = magVec*sin(theta);

if offset < offLim && jj < length(segment)

jj = jj + 1;

elseif jj == (optLevel + startIndex)

%If the very next point is not in line, don't change

%the data, then move up a point

startIndex = startIndex + 1;

jj = jj + 1;

%recompute unit vector

unitVector = unitVectorUnweight(segment,startIndex,optLevel);

else

for k = startIndex:(jj-3)

segment(1+startIndex,:) = [];%Removes points between endpoints of a continuous direction

end

startIndex = startIndex + 1;%Increment the Starting Position

jj = optLevel + startIndex;%Move jj's starting position up by the number of optimizations that have been done.

if jj <= length(segment)

%recompute the unit vector if there are enough points

unitVector = unitVectorUnweight(segment,startIndex,optLevel);

end

end

end

end

passSegmentsUn{ii} = segment;

end

Matlab 3

function unweightedUnitVector = unitVectorUnweight(segment,start,optLevel)

%This function should only be called after checking that the segment is

%long enough

%use 4 points to make sure that there is a pattern

Points = segment(start:start+optLevel-1,:);

%Compute vector(s), measured from point 1

Vec = [Points(2:optLevel,:)] - Points(1,:);

%Magnitudes of each vector for weighting

Mag = sqrt(sum(Vec.^2,2));

%Unit Vectors in each direction

uVec = Vec./Mag;

%Compute the final unit vector

if optLevel > 2

unweightedUnitVector = sum(uVec)./length(Vec);

else

unweightedUnitVector = uVec;

end

end

Enjoy!!!

20250327 175039