commit 87f7d354c44ea1f57efafc929b8aa5d05f56ba57
parent 76d1f2df166c998341f5814656bba8f43fba8d1b
Author: thing1 <thing1@seacrossedlovers.xyz>
Date: Sat, 6 Dec 2025 19:52:04 +0000
powered through report
Diffstat:
6 files changed, 489 insertions(+), 51 deletions(-)
diff --git a/CS12020/robot/calibrate/calibrate.ino b/CS12020/robot/calibrate/calibrate.ino
@@ -6,29 +6,26 @@ void
setup() {
Serial.begin(9600);
+ /* black calibration */
SEprintf("expecting to be on black\n");
delay(3000);
+
+ /* repeatedly changes calibration values for each ldr until returning consistant data */
while (isLDRBright(LDRA)) LDRA_SWITCH++;
while (isLDRBright(LDRB)) LDRB_SWITCH++;
while (isLDRBright(LDRC)) LDRC_SWITCH++;
- SEprintf("%d, %d, %d", LDRA_SWITCH + 100, LDRB_SWITCH + 100, LDRC_SWITCH + 100);
+ SEprintf("%d, %d, %d\n\n", LDRA_SWITCH + 100, LDRB_SWITCH + 100, LDRC_SWITCH + 100);
EEPROM.put(6, LDRA_SWITCH + 100);
EEPROM.put(10, LDRB_SWITCH + 100);
EEPROM.put(14, LDRB_SWITCH + 100);
- SEprintf("expecting to be on white\n");
- delay(3000);
- while (!isLDRBright(LDRA)) LDRA_SWITCH++;
- while (!isLDRBright(LDRB)) LDRB_SWITCH++;
- while (!isLDRBright(LDRC)) LDRC_SWITCH++;
-
- SEprintf("%d, %d, %d", LDRA_SWITCH + 100, LDRB_SWITCH + 100, LDRC_SWITCH + 100);
-
- EEPROM.put(4, LDRA_SWITCH + 100);
- EEPROM.put(8, LDRB_SWITCH + 100);
- EEPROM.put(12, LDRB_SWITCH + 100);
+ /* setting robots values, my robots values for fwd motion were the same on both servos so i just use SPEED, other robots will need different values */
+ EEPROM.put(0, (uint8_t)SERVOA_ZERO);
+ EEPROM.put(1, (uint8_t)SPEEDA);
+ EEPROM.put(2, (uint8_t)SERVOB_ZERO);
+ EEPROM.put(3, (uint8_t)SPEEDB);
}
void
diff --git a/CS12020/robot/calibrate/helper.h b/CS12020/robot/calibrate/helper.h
@@ -1,3 +1,5 @@
+#ifndef __HELPER_H_
+#define __HELPER_H_
#include <Servo.h>
#include <stdint.h>
@@ -15,9 +17,10 @@
#define LDRB A1
#define LDRC A0
-#define SERVOA_ZERO 84
-#define SERVOB_ZERO 85
-#define SPEED 10
+uint8_t SERVOA_ZERO = 84;
+uint8_t SERVOB_ZERO = 85;
+uint8_t SPEEDA = 10;
+uint8_t SPEEDB = 10;
#define ANALOG_MAX 1023
@@ -65,11 +68,11 @@ setupRobot() {
void
SEprintf(const char *fmt, ...) {
- static char sbuf[64] = { 0 };
+ char sbuf[256] = { 0 };
va_list ap;
va_start(ap, fmt);
- vsnprintf(sbuf, 64, fmt, ap);
+ vsnprintf(sbuf, 256, fmt, ap);
va_end(ap);
Serial.print(sbuf);
@@ -107,9 +110,11 @@ setLEDs(int g, int y, int r) {
void
waitButton(int pin, int state) {
- while (digitalRead(pin) == state) ;
+ while (digitalRead(pin) != !state) ;
+ delay(50);
+ while (digitalRead(pin) != state) ;
+ delay(50);
}
-
bool
isButtonAPressed() {
return !digitalRead(BUTTONA);
@@ -119,3 +124,4 @@ bool
isButtonBPressed() {
return !digitalRead(BUTTONB);
}
+#endif
+\ No newline at end of file
diff --git a/CS12020/robot/line/line.ino b/CS12020/robot/line/line.ino
@@ -249,7 +249,6 @@ checkBarcode(state *s)
s->timeStamp = t;
}
-/* TODO check if dirs are correct */
void
move(state *s)
{
@@ -328,22 +327,22 @@ void
loop()
{
state *s = getState();
-
+
/* do nothing while there is an obstacle */
while (isObstacle()) {
flash(YELLOW_LED, s);
return;
}
-
- /* the robots on a barcode */
+
+ /* the robots on a barcode */
if (s->right && s->left && s->mid) {
checkBarcode(s);
-
+
/* run through the barcode */
for (int i = 0; i < 20; i++)
- stepMove(FWD, s);
+ stepMove(FWD, s);
}
-
+
/* normal movement */
else move(s);
-}
+}
+\ No newline at end of file
diff --git a/CS12020/robot/line/reading.h b/CS12020/robot/line/reading.h
@@ -3,7 +3,7 @@
/* READING_INTERVAL should be declared by the caller program before this code is included */
typedef struct reading {
- int left, mid, right;
+ unsigned int left, mid, right;
} reading;
reading
@@ -13,32 +13,24 @@ readSensors()
}
reading
-addreadings(reading r1, reading r2)
-{
- return (reading){r1.left + r2.left, r1.mid + r2.mid, r1.right + r2.right};
-}
-
-reading
-divreadings(reading r, int n)
-{
- return (reading){r.left / n, r.mid / n , r.right / n};
-}
-
-reading
readSensorsMean(int readings)
{
- reading rs[readings], r;
- for (int i = 0; i < readings; i++) {
- rs[i] = readSensors();
- delay(READING_INTERVAL);
- }
+ reading r;
+ unsigned int left = 0, mid = 0, right = 0;
for (int i = 0; i < readings; i++) {
- SEprintf("single:%d,%d,%d\n", rs[i].left, rs[i].mid, rs[i].right);
- r = addreadings(r, rs[i]);
+ r = readSensors();
+ left += r.left;
+ mid += r.mid;
+ right += r.right;
+ SEprintf("single:%d,%d,%d\n", r.left, r.mid, r.right);
+ delay(READING_INTERVAL);
}
- r = divreadings(r, readings);
- SEprintf("mean:%d,%d,%d\n", r.left, r.mid, r.right);
+ left /= readings;
+ mid /= readings;
+ right /= readings;
+ SEprintf("mean:%u,%u,%u\n", left, mid, right);
+ r = (reading){left, mid, right};
return r;
-}
-\ No newline at end of file
+}
diff --git a/CS12020/robot/report/writeup/Makefile b/CS12020/robot/report/writeup/Makefile
@@ -0,0 +1,6 @@
+writeup.pdf: writeup.tex
+ pdflatex writeup.tex
+monitor: writeup.pdf
+ zathura writeup.pdf &
+clean:
+ rm *.pdf *.aux *.log
diff --git a/CS12020/robot/report/writeup/writeup.tex b/CS12020/robot/report/writeup/writeup.tex
@@ -0,0 +1,437 @@
+\documentclass[a4paper,12pt]{article}
+
+\usepackage{tabularx}
+\usepackage{geometry}
+\usepackage{titling}
+\usepackage{titlesec}
+\usepackage[english]{babel}
+\usepackage[hidelinks]{hyperref}
+\usepackage{listings}
+\usepackage{graphicx}
+\usepackage{float}
+
+\graphicspath{ {./images} }
+
+\titleformat{\section} {\large\bfseries} {} {0em} {}[\titlerule]
+\titleformat{\subsection} {\bfseries} {} {0em} {}[\titlerule]
+\geometry{a4paper,total={170mm,257mm},left=25mm,right=25mm}
+\setlength{\parindent}{0pt}
+\setlength{\parskip}{10pt}
+
+\author{Lucas Standen}
+\title{Robotics report}
+
+
+\begin{document}
+\maketitle
+\newpage
+\section{How does the robot work?}
+I designed my system around the idea of repeatedly polling the sensors, and adjusting the motors based on the input, I made functions called \lstinline{stepMove} and \lstinline{stepTurn} which each moved or turned the robot a small amount. These function ran in a very short amount of time, allowing them to be called as often as possible, letting me read the sensors more often. This aloud me to make my robot react to changes, such as going off course or touching the barcode, a lot faster than if I halted the program while the robot moved. Thanks to this I didn't have to worry about distances or angles in my code, as I could tell the robot to keep moving until something happened.
+
+The sensors were polled by a function called \lstinline{getState}, which would be called as often as possible and updated the readings of the sensors in the robot, and updated the state of the motors depending on how long they had be turned on for. My step function worked by storing a time stamp of when they were called (using \lstinline{millis}), then every time the get state function was called, I could see if it had been over 19 milliseconds since the motors had been told to move, if it had been, I could turn them off. The reason I did this was to avoid the need to turn the motors off in my behavior functions and line following functions, as having that in them, would have made them more complex than they needed to be.
+
+These functions were used to create a line following function, 2 behavior functions and a barcode detection function. They are all called from the Arduino's loop function, allowing them to be called as often as possible.
+
+\subsection{What went well?}
+I was very happy with the robots attraction and avoidance behaviors, they both worked very well, and were reliable. The LED state functioned as expected, flashing repeatedly, this worked in a similar way to the motors, checking when the last call was made to flash the LED, allowing the code to spend more time on the sensors readings than the state of the LED.
+
+I was also happy with my use of a state \lstinline{struct} to hold all of the robots internal values, thanks to this my robot used almost no global variables. I declared it as \lstinline{static} inside of the \lstinline{getState} function, this meant that I could reassign the state variable in my \lstinline{loop} function, while not losing data, such as LED flash state.
+
+A final thing that I was proud of in my code was my debug macros, I wrote a macro that aloud entire code segments to be toggled, in a neat and clean way. I used this a lot over my code to get readings from all the sensors. It also is used to enable testing upon starting the robot.
+
+\subsection{What didn't go well?}
+
+I found my robot can sometimes get stuck while reading barcodes, if it did not enter them straight on, it would think that it needs to turn. This was partially fixed by adding an error correction, that would try to detect when this happened and turn in the opposite direction, to line the robot up with the barcode. This did help, however it still got stuck sometimes.
+
+The robots turning while in line following mode can be very rigid. My robot would stop moving forward when it needed to turn and would then allow itself to keep moving. This often led to over or under correction. This could sometimes cause the robot to jitter on the line trying to course correct.
+
+\subsection{Analysing the code}
+
+\noindent\begin{minipage}{\textwidth}
+The following code shows the \lstinline{loop} function of my code, this is where the majority of my logic is. Thanks to the smaller helper functions that I made, this can be very simple to modify if needed.
+
+\begin{verbatim}
+void
+loop()
+{
+ state *s = getState();
+
+ /* do nothing while there is an obstacle */
+ while (isObstacle()) {
+ flash(YELLOW_LED, s);
+ return;
+ }
+
+ /* the robots on a barcode */
+ if (s->right && s->left && s->mid) {
+ checkBarcode(s);
+
+ /* run through the barcode */
+ for (int i = 0; i < 20; i++)
+ stepMove(FWD, s);
+ }
+
+ /* normal movement */
+ else move(s);
+}
+\end{verbatim}
+\end{minipage}
+
+\noindent\begin{minipage}{\textwidth}
+The following code shows my \lstinline{getState} functions, and other helping functions, these all worked together to make the code simpler in the main body of the program, which keeps the code maintainable.
+
+\begin{verbatim}
+typedef struct state {
+ bool left, mid, right;
+ int rawleft, rawmid, rawright;
+ enum DIRECTIONS prevDir;
+ long timeStamp;
+ long lastmove, lastturn;
+ long gstate, ystate, rstate;
+} state;
+
+void
+stepMove(int dir, state *s)
+{
+ /* move the robot fwd or bwd */
+ servoA.write(SERVOA_ZERO + (SPEEDA * dir));
+ servoB.write(SERVOB_ZERO - (SPEEDB * dir));
+ s->lastmove = millis();
+}
+
+void
+updateMotor(state *s)
+{
+ /* turns off the motor if we haven't called step
+ move/turn recently */
+ if (millis() - s->lastmove > 10 && millis() - s->lastturn) {
+ servoA.write(SERVOA_ZERO);
+ servoB.write(SERVOB_ZERO);
+ }
+}
+
+state *
+getState()
+{
+ static state s;
+
+ /* update all of the robots LDR readings */
+ s.left = !isLDRBright(LDRA);
+ s.mid = !isLDRBright(LDRB);
+ s.right = !isLDRBright(LDRC);
+ s.rawleft = analogRead(LDRA);
+ s.rawmid = analogRead(LDRB);
+ s.rawright = analogRead(LDRC);
+
+ updateFlash(&s);
+ updateMotor(&s);
+
+ return &s;
+}
+\end{verbatim}
+\end{minipage}
+
+\noindent\begin{minipage}{\textwidth}
+The following code shows my \lstinline{DBG} macro, that I've used throughout my code to help me find errors, or which state my robot is in at any point. This macro can work for any number of expressions, as the intended use case is that you pass a scope into the macro like so \lstinline`DBG({Serial.println("hello!");});`
+
+\begin{verbatim}
+#define DEBUG
+
+#ifdef DEBUG
+ #define DBG(expr) expr;
+#else
+ #define DBG(expr)
+#endif
+\end{verbatim}
+\end{minipage}
+
+\section{Comparing my code to the design brief}
+\subsection{Working with Light Dependant Resistors}
+\noindent\begin{minipage}{\textwidth}
+When the robot starts up, and has been compiled with DEBUG enabled, it will start a testing program, this program will perform all tests on the LDRs. The code that does so is presented here.
+
+\begin{verbatim}
+void
+testLDR()
+{
+ SEprintf("Testing LDRS\n");
+ for (int i = 0; i < 2; i++) {
+ SEprintf("Please turn on the lights, \
+ and place the robot on a %s surface \
+ (press button A to continue)\n", colors[i]);
+
+ waitButton(BUTTONA, HIGH);
+ readSensorsMean(TESTREADINGS);
+
+ SEprintf("Test complete, please turn off the \
+ lights (press button A to continue)\n");
+
+ waitButton(BUTTONA, HIGH);
+ readSensorsMean(TESTREADINGS);
+ }
+ SEprintf("finished testing LDRS, please check the values \
+ are expected for lighting conditions\n");
+}
+\end{verbatim}
+\end{minipage}
+
+\begin{center}
+ \textit{These lines have been wrapped, so this code will not compile.}
+\end{center}
+
+Note the use of the \lstinline{SEprintf} function, this is one of many helper functions I made. It uses \lstinline{snprintf} from the C stdlib adn then prints it using the Arduino's \lstinline{Serial} class. This makes it equivalent to the C \lstinline{printf} function.
+
+\subsection{Light source following and obstacle detection}
+\noindent\begin{minipage}{\textwidth}
+To make the robot follow the line, I made a move function that checks the current state of the LDRs and will adjusts the course accordingly.
+
+\begin{verbatim}
+void
+move(state *s)
+{
+ /* small adjustments */
+ if (s->right && s->mid)
+ stepTurn(RGT, s);
+ else if (s->left && s->mid)
+ stepTurn(LFT, s);
+
+ /* large adjustments */
+ else if (s->right) {
+ s->prevDir = LFT;
+ stepTurn(RGT, s);
+ } else if (s->left) {
+ s->prevDir = RGT;
+ stepTurn(LFT, s);
+
+ /* drive straight, nominal */
+ } else if (s->mid) {
+ for (int i = 0; i < 20; i++)
+ stepMove(FWD, s);
+ } else {
+ /* try to recover from not being on the line by
+ moving in the prevDir only updated on large
+ turns */
+
+ while (!s->mid) {
+ getState();
+ stepTurn(s->prevDir,s);
+ }
+ }
+}
+\end{verbatim}
+\end{minipage}
+
+\noindent\begin{minipage}{\textwidth}
+ To make the robot stop and wait when an obstacle, I simply have this code at the top of my loop function, if an obstacle is seen, the code will just keep on going to the top of the loop function. The \lstinline{flash} function will cause the LED to flash.
+
+\begin{verbatim}
+void
+loop()
+{
+ state *s = getState();
+
+ /* do nothing while there is an obstacle */
+ while (isObstacle()) {
+ flash(YELLOW_LED, s);
+ return;
+ }
+ ....
+\end{verbatim}
+\end{minipage}
+
+\noindent\begin{minipage}{\textwidth}
+ The barcode reading function compares the time that the robot was last on a barcode from the current time and if it is longer than a certain time, then a barcode has been passed.
+
+\begin{verbatim}
+void
+checkBarcode(state *s)
+{
+ long t = millis();
+ if (s->timeStamp > 500) {
+ /* long barcode */
+ if ((t - s->timeStamp) > 3000)
+ runBehave(&stepAvoidanceBehave);
+
+ /* short barcode */
+ else if ((t - s->timeStamp) > 800)
+ runBehave(&stepAttractionBehave);
+ }
+ s->timeStamp = t;
+}
+\end{verbatim}
+This code makes use of function pointers, this is to avoid over indenting the code, code that has been indented to much can become hard to follow.
+\end{minipage}
+
+\noindent\begin{minipage}{\textwidth}
+ The avoidance and attraction behaviors are both very similar, just having inverted directions, they work by comparing the raw values of the LDRs, meaning they should work in many light conditions.
+
+\begin{verbatim}
+void
+stepAvoidanceBehave(state *s)
+{
+ flash(RED_LED, s);
+ DBG({
+ /* WARNING this causes the program to stutter when left on,
+ not too bad for debugging but very bad for general use! */
+ SEprintf("Avoid: %d, %d, %d\n", s->rawleft, s->rawmid, s->rawright);
+ });
+
+ /* left is noticably larger */
+ if (s->rawleft > s->rawright + 50)
+ stepTurn(RGT,s);
+ /* right is noticably larger */
+ else if (s->rawright > s->rawleft + 50)
+ stepTurn(LFT,s);
+ else
+ stepMove(FWD, s);
+}
+
+
+void
+stepAttractionBehave(state *s)
+{
+ flash(GREEN_LED, s);
+ DBG({
+ SEprintf("Attract: %d, %d, %d\n", s->rawleft, s->rawmid, s->rawright);
+ });
+
+ /* left is noticably larger */
+ if (s->rawleft > s->rawright + 50)
+ stepTurn(LFT,s);
+ /* right is noticably larger */
+ else if (s->rawright > s->rawleft +50)
+ stepTurn(RGT,s);
+ else
+ stepMove(FWD, s);
+}
+
+void
+runBehave(void (*stepBehave)(state *))
+{
+ while (true) {
+ state *s = getState();
+ stepBehave(s);
+ }
+}
+\end{verbatim}
+\end{minipage}
+
+\subsection{Robot states}
+\noindent\begin{minipage}{\textwidth}
+The robots states were handled through a \lstinline{flash} function that could cause any one LED to flash at a given point.
+
+\begin{verbatim}
+void
+flash(int pin, state *s)
+{
+ switch (pin) {
+ case GREEN_LED: s->gstate = millis(); break;
+ case YELLOW_LED: s->ystate = millis(); break;
+ case RED_LED: s->rstate = millis(); break;
+ }
+}
+
+void
+stepFlash(int pin)
+{
+ /* we define this a static to preserve its value across function calls */
+ static uint8_t flashCount;
+
+ if (flashCount < FLASHTIME)
+ digitalWrite(pin, HIGH);
+ else if (flashCount > FLASHTIME / 2)
+ digitalWrite(pin, LOW);
+
+ /* this value wraps which is by design, this will cause the flashing to reset */
+ flashCount++;
+}
+
+void
+updateFlash(state *s)
+{
+ /* turn off the leds if they arent ment to be on anymore */
+ if (millis() - s->gstate > FLASHTIME) {
+ s->gstate = 0;
+ digitalWrite(GREEN_LED, LOW);
+ } if (millis() - s->ystate > FLASHTIME) {
+ s->ystate = 0;
+ digitalWrite(YELLOW_LED, LOW);
+ } if (millis() - s->rstate > FLASHTIME) {
+ s->rstate = 0;
+ digitalWrite(RED_LED, LOW);
+ }
+
+ /* turn on the leds if needed */
+ if (s->gstate) stepFlash(GREEN_LED);
+ if (s->ystate) stepFlash(YELLOW_LED);
+ if (s->rstate) stepFlash(RED_LED);
+}
+\end{verbatim}
+\end{minipage}
+
+\subsection{EEPROM}
+\noindent\begin{minipage}{\textwidth}
+The EEPROM was fairly easy to implement, my code only relied on the black calibration value, along with the motor values.
+
+\begin{verbatim}
+void
+setup()
+{
+ /* let the user attach the battery, attaching the battery while code is running caused strange behavior */
+ delay(3000);
+ setupRobot();
+
+ /* read the calibrated values that should be in EEPROM, these values will be made by the calibrate program */
+ EEPROM.get(6, LDRA_SWITCH);
+ EEPROM.get(10, LDRB_SWITCH);
+ EEPROM.get(14, LDRC_SWITCH);
+
+ /* servo speed values */
+ EEPROM.get(0, SERVOA_ZERO);
+ EEPROM.get(1, SPEEDA);
+ EEPROM.get(2, SERVOB_ZERO);
+ EEPROM.get(3, SPEEDB);
+ ....
+\end{verbatim}
+\end{minipage}
+
+
+\section{Testing}
+\begin{table}[H]
+ \resizebox{\textwidth}{!}{
+ \begin{tabular}{|| l | p{0.6\textwidth} | r ||}
+ \hline
+ \textbf{Test} & \textbf{Method} & \textbf{Result} \\
+ \hline
+ \hline
+ Push-button left &
+ I made a function called \lstinline{testButton} repeatedly asks the user to press the button, counting how many times the program receives the button press, the user can compare this to how many times the button was pressed. &
+ Pass \\
+ \hline
+ Push-button right &
+ My \lstinline{testButton} function is requires an argument of the pin number, meaning a button on any pin could be tested. &
+ Pass \\
+ \hline
+ Servo left &
+ I made a \lstinline{testServo} function, it spins the servo at its default speed for 10 seconds, the user can see if this acts as expected. &
+ Pass \\
+ \hline
+ Servo right &
+ Like with the \lstinline{testButton} function, any value can be passed into this function, allow the user to test the other servo. &
+ Pass \\
+ \hline
+ IR transmitter \& receiver &
+ I made a function called \lstinline{testIR} that expects the user to hold an item in front of the sensor and takes multiple readings, checking if the value outputted was consistent for more than 8 times out of 10. &
+ Pass \\
+ \hline
+ LDRs &
+ I made a function called \lstinline{testLDRS} that asks the user to place the robot on different colours and in different lighting conditions, then takes many readings, the user can then compares these to expected values for the input. &
+ Pass \\
+ \hline
+ \end{tabular}
+ }
+\end{table}
+
+While testing my robot, I found a few issues of note. For example, while testing the IR sensor, it was always seeing something, even when nothing was present in front of it. I found this was because it was pointing inwards slightly, after turning it to face outwards, it started working as expected. I also found issues while testing with the LDRs, my testing program was receiving somewhat random offsets. I realised this was because I had not assigned the starting values to 0, so I was reading memory garbage.
+
+\End{document}