// Kayla Frost
// 300390878
// ENGR 1190 Final Exam, Tasks 1 - 4
// April 20 2025
// Include servo library for the Servo motor
#include <Servo.h>
// Include FastLED library for the NeoPixel ring
#include <FastLED.h>
// Define pin numbers to led colors
int aled = 11; // Amber LED is attached to pin 11
int bled = 10; // Blue LED is attached to pin 10
int rled = 9; // Red LED is attached to pin 9
int gled = 8; // Green LED is attached to pin 8
// Define pin numbers to button letters
int butd = 6; // Yellow/amber button is attached to pin 6
int butc = 7; // Blue button is attached to pin 7
int butb = 3; // Green button is attached to pin 3
int buta = 2; // Red button is attached to pin 2
int servoPin = 12; // Servo is attached to pin 12
Servo myservo; // The servo motor is called myservo
int pos = 0; // Variable to store servo motor's position (in degrees)
#define neoNumCells 16 // The neopixel has 16 cells
#define neoData 13 // Neopixel is attached to pin 13
CRGB NeoArray[neoNumCells]; // Define neopixel array with number of cells in ring (16 cells)
void buttonA_interrupt(); // Prototype for button A's interrupt function
volatile bool startInterrupt = false; // for Task 1, this changes to true when button A is pressed, otherwise it stays as false and Task 1 will not execute
// volatile used since these variables are used with the interrupt and will be altered in the code
volatile int buttonAPressCount = 0; // for Task 1, this is a counter for the times button A has been pressed
void setup() // put your setup code here, to run once:
{
// Define all pins connected to LEDs as outputs
pinMode(aled, OUTPUT);
pinMode(bled, OUTPUT);
pinMode(rled, OUTPUT);
pinMode(gled, OUTPUT);
// Define all pins connected to buttons as inputs
pinMode(butd, INPUT);
pinMode(butc, INPUT);
pinMode(butb, INPUT);
pinMode(buta, INPUT);
// Initially, turn all LEDs off (located in pins 8 to 11)
for (int pin = 8; pin <= 11; pin++)
{
digitalWrite(pin, HIGH); // in specified pin, turn LED to high (off)
}
//Servo Motor set up
myservo.attach(servoPin); // attach servo motor to its pin (pin 12)
myservo.write(20); // move servo motor to an initial position of 20 degrees
//NeoPixel set up --> ring array with 16 cells
FastLED.addLeds<NEOPIXEL, neoData>(NeoArray, neoNumCells); // < NEOPIXEL , data> (array name , number of cells)
// initially, change all pixel cells on the neopixel to black
for (int x = 0; x <= 15; x++) // x represents the pixel cell number from cell 0 to cell 15
{
NeoArray[x] = CRGB::Black; // change cell to black
FastLED.show();
}
// this sets up the rising interrupt by button A in pin 2 and calls the interrupt function
attachInterrupt(digitalPinToInterrupt(buta), buttonA_interrupt, RISING);
}
void loop() // put your main code here, to run repeatedly:
{
/*
Task 1:
When button A (RED) is pressed, it generates a rising edge interrupt that enables/disables the system default behaviour.
When engaged via the interrupt, the system default behaviour continuously, and randomly chooses between the following 4 states:
The green LED blinks one time, the red LED blinks two times, the blue LED blinks three times and the amber LED blinks four times.
*/
// the counter starts at 0
// when pressed once, it increments to 1 --> this should trigger the interrupt
// so when divided by two, the remainder will be 1 --> change startInterrupt to true
// when pressed again, it increments to 2 --> this should end the interrupt
// so when divided by two, the remainder will be 0 --> change startInterrupt to false
int countRemainder = buttonAPressCount % 2; // to find the remainder of the counter for button A when divided by two
if(countRemainder == 1) // if the remainder is 1,
{
startInterrupt = true; // interrupt will begin in loop function
}
if(countRemainder == 0) // if the remainder is 0,
{
startInterrupt = false; // interrupt will terminate and reset
}
// BEGIN INTERRUPTION
if (startInterrupt) // if startInterrupt is changed to true by the interrupt function, triggered by every other press of button A, then this loop will run
{
int randomLED = 0; // integer to hold randomly generated number for Task 1
// generate random number between pins for LEDs (between 8 and 11)
randomLED = random(gled, aled + 1); // website says (min, max - 1)
int numBlinks = randomLED - 7; // number of blinks correlated to the pin number *pattern: pin number minus 7 = number of blinks for that LED
for(int blink = 1; blink <= numBlinks; blink++) //for blink 1 to maximum number of blinks calculated by pattern of the pin number
{
// blink once per run of this loop
digitalWrite(randomLED, LOW); // chosen LED on
delay(150); // on for 150 ms
digitalWrite(randomLED, HIGH); // chosen LED off
delay(150); // off for 150 ms
}
delay(500); // half a second pause between next random LED selection and action
// (because sometimes the same led is picked twice in a row and it blinks double without a break between)
}
// TERMINATE INTERRUPTION AND RESET LIGHTS
if (startInterrupt == false) // if startInterrupt is changed to false by the interrupt function, triggered by every other press of button A, then this loop will run
{
// turn all of the LEDs off because the interruption is terminated
digitalWrite(gled, HIGH); // green LED off
digitalWrite(rled, HIGH); // red LED off
digitalWrite(bled, HIGH); // blue LED off
digitalWrite(aled, HIGH); // amber LED off
}
int servo_pos = 0; // for Task 2 and 3, this variable holds the servo motor's position (in degrees)
/*
Task 2:
While button B (GREEN) is pressed, the servo rotates slowly back and forth from 20° to 125°,
stopping at every intermediate single degree position for 12 ms.
On the forward stroke, the green LED turns on at 25°, the red LED turns on at 49°, the blue LED turns on at 81° and the amber LED turns on at 100°.
On the return stroke, the LEDs turn off at the same positions stated above.
*/
while (digitalRead(butb)) // while button B is pressed, the function returns true, so task 2's while loop will run
{
// FORWARD STROKE
for (servo_pos = 20; servo_pos <= 125; servo_pos++) // servo position begins at 20°, increments 1° each loop and ends at 125°
{
myservo.write(servo_pos); // assign the current position of the motor in degrees
delay(12); // wait 12 ms before moving to next position
if (servo_pos == 25) // when servo reaches 25°
{
digitalWrite(gled, LOW); // turn on green LED
}
if (servo_pos == 49) // when servo reaches 49°
{
digitalWrite(rled, LOW); // turn on red LED
}
if (servo_pos == 81) // when servo reaches 81°
{
digitalWrite(bled, LOW); // turn on blue LED
}
if (servo_pos == 100) // when servo reaches 100°
{
digitalWrite(aled, LOW); // turn on amber LED
}
}
// RETURN STROKE
for (servo_pos = 125; servo_pos >= 20; servo_pos--) // servo position begins at 125°, decrements 1° each loop, and ends at 20°
{
myservo.write(servo_pos); // assign the current position in degrees to the motor
delay(12); // wait 12 ms before moving to next position
if (servo_pos == 25) // when servo reaches 25°
{
digitalWrite(gled, HIGH); // turn off green LED
}
if (servo_pos == 49) // when servo reaches 49°
{
digitalWrite(rled, HIGH); // turn off red LED
}
if (servo_pos == 81) // when servo reaches 81°
{
digitalWrite(bled, HIGH); // turn off blue LED
}
if (servo_pos == 100) // when servo reaches 100°
{
digitalWrite(aled, HIGH); // turn off amber LED
}
}
}
/*
Task 3:
While button C (BLUE) is pressed, the servo rotates slowly back and forth from 20° to 175°,
stopping at every intermediate 5° position for 60 ms.
On the forward stroke, each Neopixel cell illuminates in a red, green, the blue pattern every 10°, starting when the servo is at 25°.
On return stroke, each Neopixel cell turns off in reverse order every 10°, starting when the servo is at 170°.
*/
while (digitalRead(butc)) // while button C is pressed, the function returns true, so task 3's while loop will run
{
int cell = 0; // this variable holds the neopixel cell number
// FORWARD STROKE
for (servo_pos = 20; servo_pos <= 175; servo_pos += 5) // servo position begins at 20°, increments 5° each loop, and ends at 175°
{
myservo.write(servo_pos); // assign the current position in degrees to the motor
delay(60); // wait 60 ms before moving to next position
if (servo_pos == 25 || servo_pos == 55 || servo_pos == 85 || servo_pos == 115 || servo_pos == 145 || servo_pos == 175) // if the servo is at 25°, 55°, 85°, 115°, 145° or 175°
{
NeoArray[cell] = CRGB::Red; // change the current cell to red
FastLED.show();
cell++; // increment cell number to move to the next cell on the neopixel
}
if (servo_pos == 35 || servo_pos == 65 || servo_pos == 95 || servo_pos == 125 || servo_pos == 155) // if the servo is at 35°, 65°, 95°, 125° or 155°
{
NeoArray[cell] = CRGB::Green; // change the current cell to green
FastLED.show();
cell++; // increment cell number to move to the next cell on the neopixel
}
if (servo_pos == 45 || servo_pos == 75 || servo_pos == 105 || servo_pos == 135 || servo_pos == 165) // if the servo is at 45°, 75°, 105°, 135° or 165°
{
NeoArray[cell] = CRGB::Blue; // change the current cell to blue
FastLED.show();
cell++; // increment cell number to move to the next cell on the neopixel
}
}
// RETURN STROKE
int givenPosition = 170; // represents the position of the motor we are looking for (as the servo should start moving at 170°)
cell = 15; // change cell to cell 15, because each cell turns back to black starting from cell 15 to cell 0
for (servo_pos = 175; servo_pos >= 20; servo_pos -= 5) // servo position begins at 175°, decrements 5° each loop, and ends at 20°
{
myservo.write(servo_pos); // assign the current position in degrees to the motor
delay(60); // wait 60 ms before moving to next position
if (servo_pos == givenPosition) // when the motor's position matches the next one we're looking for
{
NeoArray[cell] = CRGB::Black; // change the current cell to red
FastLED.show();
cell--; // decrement cell number to move to the next cell on the neopixel
givenPosition -= 10; // change given position to 10 degrees less than the current position
}
}
}
/*
Task 4:
While button D (YELLOW) is pressed, the NeoPixel lights up red in the ith cell,
green in the [i+4th] cell, blue in the [i+8th] cell, and orange in the [i+12th] cell.
Simultaneously, the LEDs light up green, red, blue and amber in sync with the NeoPixels in cells 0-3 and 8-11,
and the LEDs light up amber, blue, red and green in sync with cells 4-7 and 12-15.
*/
while(digitalRead(butd)) // while button D is pressed, the function returns true, so task 4's while loop will run
{
for(int pix = 0; pix < neoNumCells; pix++) // pix represents the pixel cell number, ranging from cell 0 to cell 15
{
// using modulus %, each integer represents that cell's intended color (so the cell number does not go over 15)
int redCell = pix % neoNumCells; // red for [ith] cell
int greenCell = (pix+4) % neoNumCells; // green for [i + 4th] cell
int blueCell = (pix+8) % neoNumCells; // blue for [i + 8th] cell
int orangeCell = (pix+12) % neoNumCells; // orange for [i + 12th] cell
// once correct cell is determined for each color, turn those cells their intended color for one execution of the for loop
NeoArray[redCell] = CRGB::Red;
NeoArray[greenCell] = CRGB::Green;
NeoArray[blueCell] = CRGB::Blue;
NeoArray[orangeCell] = CRGB::Orange;
FastLED.show();
/*
pixel cells 0, 1, 2, 3 correspond with LEDs going green, red, blue, amber
pixel cells 4, 5, 6, 7 correspond with LEDs going amber, blue, red, green
pixel cells 8, 9, 10, 11 correspond with LEDs going green, red, blue, amber
pixel cells 12, 13, 14, 15 correspond with LEDs going amber, blue, red, green
so,
green LED turns on for cells 0, 7, 8 and 15
red LED turns on for cells 1, 6, 9 and 14
blue LED turns on for cells 2, 5, 10 and 13
amber LED turns on for cells 3, 4, 11 and 12
*/
if(pix == 0 || pix == 7 || pix == 8 || pix == 15) // green LED turns on for cells 0, 7, 8 and 15
{
digitalWrite(gled, LOW); // turn on green LED
delay(75); // on for 75 ms
digitalWrite(gled, HIGH); // turn off green LED
delay(75); // off for 75 ms
}
if(pix == 1 || pix == 6 || pix == 9 || pix == 14) // red LED turns on for cells 1, 6, 9 and 14
{
digitalWrite(rled, LOW); // turn on red LED
delay(75); // on for 75 ms
digitalWrite(rled, HIGH); // turn off red LED
delay(75); // off for 75 ms
}
if(pix == 2 || pix == 5 || pix == 10 || pix == 13) // blue LED turns on for cells 2, 5, 10 and 13
{
digitalWrite(bled, LOW); // turn on blue LED
delay(75); // on for 75 ms
digitalWrite(bled, HIGH); // turn off blue LED
delay(75); // off for 75 ms
}
if(pix == 3 || pix == 4 || pix == 11 || pix == 12) // amber LED turns on for cells 3, 4, 11 and 12
{
digitalWrite(aled, LOW); // turn on amber LED
delay(75); // on for 75 ms
digitalWrite(aled, HIGH); // turn off amber LED
delay(75); // off for 75 ms
}
// *I added this small pause because the neopixel cells look like they are blinking in the video on blackboard
// (instead of just travelling in a circular path)
delay(75);
// turn all colored NeoPixel cells back to black
NeoArray[redCell] = CRGB::Black;
NeoArray[greenCell] = CRGB::Black;
NeoArray[blueCell] = CRGB::Black;
NeoArray[orangeCell] = CRGB::Black;
FastLED.show();
// *I added this small pause because the neopixel cells look like they are blinking in the video on blackboard
delay(75);
}
}
}
// for Task 1, this is the interrupt function that is called when button A is pressed
void buttonA_interrupt()
{
buttonAPressCount++; // button press counter increments each time the interrupt function is called (which is each time the button is pressed)
// this affects whether the button is turning the task on or off
}