The Professional Mapper 9001
by DownscaledHats in Circuits > Arduino
16 Views, 1 Favorites, 0 Comments
The Professional Mapper 9001

For our project we wanted to have an Ultrasonic sensor that would spin around and do a full 360 degree reading of the room and fully map out the distance between it and the surrounding objects.
Supplies
- You will need a sparkfun kit.
- You will also need to get your hands on a stepper motor we used ROB-10551 (link: Small Stepper Motor) for our project. (If you use a servo motor instead it should work but you will likely need to change a lot of values in the code)
- If you have access to a 3D printer that's ideal for making the part, we used to attach the ultrasonic sensor to the motor.
- If you don't have access to a motor, you can probably make do with cardboard, glue, and tape.
Setting Up the Variables for Code
We must initialize and setup our variables first in order for the code in the setup() and loop() to function without error. What each variable is named does not necessarily matter, but they have to make sense in terms of what they are supposed to represent.
- Each variable that is representing a pin will have an "int" placed before the variable name to identify it as an integer (optionally, one could also put a "const" before the "int" to denote these integer variables never change value, but since we are not changing these values anyways, typing "const" first will not affect the functionality of the code in any way, hence optional.)
Code Inside Void Setup
The purpose of the void setup() function is to configure the pins we have attached on the main board. This means configuring whether each pin is an input or output using the Arduino built-in function pinMode(pin, mode), where "pin" corresponds to either the direct pin number or the integer variable that represents the pin number (as declared in step one) and "mode" corresponds to either "INPUT", "INPUT_PULLUP", or "OUTPUT"
- Open the Serial (USB) port with Serial.open(9600); to allow communication between the board and the computer,
- Attach the stepper motor to the specified stepper motor pin (here, we have to use the .attach() function because pinMode() does not handle the pulses, only the direction of the pins,
- Configure button as "INPUT_PULLUP",
- Configure ultrasonic_value (I.e., the reading received from the ultrasonic sensor) as "INPUT",
- Configure ultrasonic (I.e., the pin connected to the sensor which is responsible for sending the ultrasonic pulse) as "OUTPUT".
Code for StepMotor Function
After the void setup create a function called void stepMotor(int step). In this function you will need to create four different cases for how the high and low in the motor can be set up. This code is necessary to communicate to the stepper motor where each step reverses the polarity by activating the motor and rotating it in certain directions (depending on the motor you use this might be unnecessary). For example:
switch (step % 4) { //determine which case will be used
case 0:
digitalWrite(AIN1, HIGH); digitalWrite(AIN2, LOW);
digitalWrite(BIN1, HIGH); digitalWrite(BIN2, LOW);
break;
After you set up the four cases set both the pins to the speed of 255.
If Statement and First for Loop
The first part of the void loop is an if statement checking if the button has been pressed, after that the code will delay(50) and then use another if statement to check if the button is still being pressed. If the button is still being pressed the trial will increase and the code will finally initialize causing the motor to turn in 7.5 degree intervals, getting ultrasonic readings in the process until it turns a full 360 degrees.
- For (i = 0; i < Num_Readings; i++) the for loop will set I=0 at the start until i is less than or equal to 48 (which is the value Num_Readings should be set to when you make your integers) with i increasing by one after each loop.
- In the for loop the motor will turn 7.5 degrees times I and will turn the ultrasonic sensor to low, delay by 2 microseconds, turn the ultrasonic sensor to high and then delay by 10 microseconds, finally it will turn the ultrasonic sensor to low. Afterwards the code will take these ULTRASONIC_ECHO and plug them into a new value called echoTime.
- After getting the values from the ultrasonic sensor in the same for loop it will create a new value called distance[I] which will calculate distance using the formula echoTime * 0.0343/2;.
- We will create a new value called angle[i] which is i*7.5, and then delay(50);.
- At the end of the for loop we will do a series of prints so that the values gained from the first for loop can be visible. The first will be Serial.print(trial) so that we know what stage it is from. The second is Serial.print(angle[i]) so we know what angle the distance value was taken from. Finally the last is Serial.print(distance[i]) to print the value gained from the distance calculation taken at that angle.
The End Steps of the Code




- Next we will create a new for statement where i = Num_Readings, is > 0, and decreases by 1 after every loop, i--.
- In the for loop the motor will turn by (i) and then delay(100) after every step. This is so the motor will return to its original position.
- Finally at the end of the code there will be a while statement checking if the button is still being pressed and if it is delay by 50 until the button is released before you can start the code again.
- Also here is our full code as well just in case you need it.
Downloads
Making the Board in TinkerCad/Physical Version




The images above will hopefully be enough to help you create the physical version. Your wiring will need to change depending on the motor you use but apart from that it should work fine.
How to Attach the Motor and Sensor Together
- Grab two small pieces of wood (in our case, we decided to use two broken pieces of a popsicle stick). Join them with glue to the motor axle such that both are "sandwiching" the axle and are pointing away from the face of the motor (NOTE: make sure the glue is ONLY securing the axle and the sticks, and NOT the face of the motor too, as that would impede the ability of the axle to rotate),
- Place the sensor atop the popsicle sticks and secure it to them with duct tape (the shape should resemble something like a "T" before being taped over) without covering the actual sensors (the two circular objects on the board).
Creating the Matlab Code




Finally, we will also need to create a code in Matlab to create a graph using the data collected by the ultrasonic sensor. The code works by listening for data from the Arduino board its connected to in the form of trial, angle(degrees), and distance(cm).
- The code will check the current trial to prevent repeats and stores the values for that trial.
- Filter out invalid data of anything greater than 180 cm and less than 0 cm.
- The code converts the polar coordinates (angle, distance) into Cartesian (x, y) and then plot all the data points onto a graph.
Running the Code
Before attempting to run your code, ensure that everything is fully seated on the breadboard and the Arduino board. Additionally, ensure the Arduino board is fully connected to your computer (with the kit's included USB Type A to Type B cable) so that the code can be successfully sent to the board. Ensure the Arduino code and the MATLAB code are located within the same folder so one file can find the other file (and add the directory path of the folder to the source path of MATLAB, if it is not already).
Also here is a link to a video we filmed going over how the code works (the file was too big to attach unfortunately).
MATLAB Code
% === SERIAL PLOTTING SCRIPT ===
% MADE/ASSISTED BY CHATGPT
clc; % Clear command window
clf; % Clear current figure
clear; % Clear all variables from workspace
% === SET UP SERIAL PORT ===
comPort = "COM3"; % Define the COM port for serial communication (change if necessary)
baudRate = 9600; % Define the baud rate for communication (must match the device's rate)
s = serialport(comPort, baudRate); % Create a serialport object to connect to the specified port with the given baud rate
% Use onCleanup to ensure that the serial port is properly closed when the script exits
cleanupObj = onCleanup(@() cleanupSerial(s));
% onCleanup creates an object that runs the given function (cleanupSerial) when the variable is cleared, ensuring resource cleanup
% === INITIALIZE VARIABLES ===
prevTrial = -1; % Track the trial number (initialize to -1 to ensure first comparison triggers correctly)
angleList = []; % Array to store angles for current trial
distanceList = []; % Array to store distances for current trial
disp("Listening for serial data..."); % Display message to indicate readiness
% === BEGIN TRY BLOCK TO HANDLE ERRORS AND CLEAN EXIT ===
try
while true % Infinite loop to continuously read incoming serial data
line = readline(s); % Read one line of serial data as a string
data = sscanf(line, '%f, %f, %f');% Parse the string into three floating-point numbers: trial, angle, and distance
if numel(data) == 3 % Proceed only if exactly three values were successfully read
trial = data(1); % Extract trial number
angle_deg = data(2); % Extract angle in degrees
distance = data(3); % Extract distance in centimeters
% === DETECT START OF A NEW TRIAL ===
if trial ~= prevTrial % If trial number changes, a new trial has started
if prevTrial ~= -1 % If this is not the first ever trial
plotTrial(prevTrial, angleList, distanceList); % Plot previous trial's data
end
angleList = []; % Reset angle list for new trial
distanceList = []; % Reset distance list for new trial
prevTrial = trial; % Update current trial tracker
end
% === FILTER INVALID DISTANCE READINGS ===
if distance < 0 || distance > 180 % If distance is out of realistic bounds (e.g., sensor max range)
distance = 0; % Replace with zero to mark as invalid reading
end
% === STORE VALID ANGLE AND DISTANCE ===
angleList(end+1) = angle_deg; % Append angle to list
distanceList(end+1) = distance; % Append distance to list
% === OPTIONAL: PRINT TO CONSOLE FOR DEBUGGING ===
fprintf('Trial %d | Angle: %.1f° | Distance: %.2f cm\n', trial, angle_deg, distance);
end
end
% === HANDLE ERRORS AND SCRIPT EXIT ===
catch ME % Catch any error (e.g., Ctrl+C interrupt or runtime error)
% === IF DATA EXISTS FROM LAST TRIAL, PLOT IT ===
if ~isempty(angleList) % Check if angleList is not empty
% isempty is a built-in MATLAB function that returns true if the variable has no elements
plotTrial(prevTrial, angleList, distanceList); % Plot the last collected trial's data
end
rethrow(ME); % Rethrow the caught error to display what went wrong
end
% === FUNCTION TO PLOT POLAR DATA AS CARTESIAN POINTS ===
function plotTrial(trialNum, angles, distances)
% Convert polar to Cartesian coordinates
x = distances .* cosd(angles); % X = r * cos(theta)
y = distances .* sind(angles); % Y = r * sin(theta)
% Initialize segment containers (for filtering out invalid points)
xSegments = {}; % Cell array to hold x-coordinates for each continuous valid segment
ySegments = {}; % Cell array to hold y-coordinates for each segment
xTemp = []; % Temporary array for current segment's x values
yTemp = []; % Temporary array for current segment's y values
for i = 1:length(distances) % Loop through all distance readings
if distances(i) == 0 % If the distance is zero, it marks a break (invalid point)
if ~isempty(xTemp) % If there's data collected for current segment
xSegments{end+1} = xTemp; % Store current segment in cell array
ySegments{end+1} = yTemp;
xTemp = []; % Reset temporary storage
yTemp = [];
end
else
xTemp(end+1) = x(i); % Add valid x point
yTemp(end+1) = y(i); % Add valid y point
end
end
% After the loop, add any remaining segment that wasn’t ended by a zero
if ~isempty(xTemp) % Again, check if last segment has data
xSegments{end+1} = xTemp;
ySegments{end+1} = yTemp;
end
% === PLOTTING SECTION ===
figure(trialNum); % Create or switch to figure identified by trial number
hold on; % Allow multiple plots in same figure without overwriting
for k = 1:length(xSegments) % Plot each segment
plot(xSegments{k}, ySegments{k}, 'bo', 'MarkerFaceColor', 'b'); % Blue filled circles
end
hold off; % Release the hold
title(['Trial ', num2str(trialNum)]); % Set figure title
xlabel('X (cm)');
ylabel('Y (cm)');
axis equal; % Equal scaling for X and Y axes
grid on; % Turn on grid
end
% === SERIAL PORT CLEANUP FUNCTION ===
function cleanupSerial(s)
disp("Closing serial port...");
if ~isempty(s) && isvalid(s) % Check if serial object exists and is valid
clear s; % Delete serial object and free port
end
disp("Serial port closed.");
end