Advertisement
Guest User

Untitled

a guest
Jan 21st, 2022
23
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Arduino 19.65 KB | None | 0 0
  1. /******************************************************************************
  2. Program: Sad Cat Fixer (Laser Tower 3000)
  3.  
  4. Description: Program to control the Laser Tower 3000. This includes manual and
  5. automatic modes, an LCD screen, a servo, a buzzer, a laser (an LED for our
  6. purposes), and an infrared remote control.
  7.  
  8. Author: Pranav Rao
  9.  
  10. Date: January 9, 2021
  11.  
  12. Arduino Resources used: digital pins 3, 4, 6, 8, and analog pins 4 and 5
  13. ******************************************************************************/
  14.  
  15. /******************************************************************************
  16. Libraries: this section contains the importing of various libraries, which are
  17. files that contain several classes, structs, and functions that are essential to
  18. controlling various components such as the remote, LCD, and servo.
  19. ******************************************************************************/
  20.  
  21. #include <IRremote.hpp>        // library to interact with the IR remote
  22. #include <LiquidCrystal_I2C.h> // library to interact with the LCD
  23. #include <Servo.h>             // library to interact with the servo
  24.  
  25. /******************************************************************************
  26. Constants: these are constant values that will be used to denote important
  27. and consistent information. They are GLOBAL variables, and therefore can be used
  28. by any function in this program.
  29. ******************************************************************************/
  30.  
  31. // declare constants to represent pins of each of the circuit components (IR,
  32. // laser, servo, buzzer)
  33. const int IR_RECEIVE_PIN = 3, LASER_PIN = 4, SERVO_PIN = 6, BUZZER_PIN = 8;
  34.  
  35. // declare constant strings for the words automatic and manual (which are
  36. // printed on the LCD)
  37. const char WORD_AUTOMATIC[] = "AUTOMATIC";
  38. const char WORD_MANUAL[] = "MANUAL";
  39.  
  40. // declare constants for the sizes of the words so that they can be printed on
  41. // the LCD
  42. const int WORD_AUTOMATIC_SIZE =
  43.     sizeof(WORD_AUTOMATIC) / sizeof(WORD_AUTOMATIC[0]);
  44. const int WORD_MANUAL_SIZE = sizeof(WORD_MANUAL) / sizeof(WORD_MANUAL[0]);
  45.  
  46. // declare a set of variables used to keep track of various factors in the
  47. // program. these variables will change throughout the program
  48. int currentTime, currentPreset, randomInterval, currentServoRotation = 0,
  49.                                                 automaticRotationSpeed = 15;
  50.  
  51. // declare two clock variables, which will be used to keep track of certain
  52. // times (amount of milliseconds from the beginning of the program). These
  53. // clocks allow for the asynchronous behaviour of the automatic mode.
  54. // clock1 is used to keep track of the intervals between preset changes in
  55. // automatic mode
  56. unsigned long clock1, clock2;
  57.  
  58. // declare two bool variables that will be used to keep track of the mode
  59. // and the status of the laser
  60. bool automatic = true, laserOn = false;
  61.  
  62. /******************************************************************************
  63. Objects: these variables are instances of classes (imported in the header files
  64. above). By making these instances, we are able to access all of the attributes
  65. and functions defined in those classes
  66. ******************************************************************************/
  67.  
  68. // declare an object to represent the LCD
  69. LiquidCrystal_I2C lcd(0x27, 16, 2);
  70. // declare an object to represent the servo motor
  71. Servo servo;
  72.  
  73. /******************************************************************************
  74. writeText function: this function is called to write certain text to the
  75. LCD. It takes a char pointer (a char array, essentially) and the length of the
  76. char array, and returns void.
  77. ******************************************************************************/
  78.  
  79. void writeText(const char *text, int len) {
  80.   Serial.println("LOG: Writing text to LCD.");
  81.  
  82.   lcd.clear(); // clear the LCD
  83.  
  84.   // start from the top left of the LCD. Then, sequencially print each character
  85.   // in the given word, one character after the next
  86.   for (int i = 0; i < len - 1; i++) {
  87.     lcd.setCursor(i, 0); // set the cursor to the correct position
  88.     lcd.print(text[i]);  // print the character
  89.   }
  90. }
  91.  
  92. /******************************************************************************
  93. toggleLaser function: this function is called toggle the state of the laser. It
  94. takes and returns nothing.
  95. ******************************************************************************/
  96.  
  97. void toggleLaser() {
  98.   // if the laser is turned on
  99.   if (laserOn) {
  100.     digitalWrite(LASER_PIN, LOW);  // turn the laser off
  101.   } else {                         // if the laser is off
  102.     digitalWrite(LASER_PIN, HIGH); // turn the laser on
  103.   }
  104.  
  105.   // if the laserOn boolean is false, set it to true else set it to false
  106.   // (essentially toggle it)
  107.   laserOn = laserOn ? false : true;
  108. }
  109.  
  110. /******************************************************************************
  111. Preset Functions: these functions are special functions that toggle different
  112. presets of this machine. They all take nothing and return nothing.
  113. ******************************************************************************/
  114.  
  115. // this preset is used to keep the laser constantly on
  116. void presetConstantOn() {
  117.   // if the laser is not on
  118.   if (!laserOn) {
  119.     toggleLaser(); // turn on the laser
  120.   }
  121. }
  122.  
  123. // this preset is used to keep the laser constantly off
  124. void presetConstantOff() {
  125.   // if the laser is on
  126.   if (laserOn) {
  127.     toggleLaser(); // turn off the laser
  128.   }
  129. }
  130.  
  131. // this preset is used only in auto mode to keep the laser blink slowly
  132. void presetAutoSlowBlink() {
  133.   unsigned long currentTime = millis();
  134.  
  135.   if (currentTime > clock2 + 1500) {
  136.     toggleLaser();
  137.     clock2 = currentTime;
  138.   }
  139. }
  140.  
  141. // this preset is used only in auto mode to make the laser blink fast
  142. void presetAutoFastBlink() {
  143.   unsigned long currentTime = millis();
  144.  
  145.   if (currentTime > clock2 + 200) {
  146.     toggleLaser();
  147.     clock2 = currentTime;
  148.   }
  149. }
  150.  
  151. // this reset is used only in manual mode to make the laser blink slow
  152. void presetManualSlowBlink() {
  153.   for (int i = 0; i < 5; i++) {
  154.     toggleLaser();
  155.     delay(2000);
  156.     toggleLaser();
  157.     delay(2000);
  158.   }
  159. }
  160.  
  161. // this reset is used only in manual mode to make the laser blink fast
  162. void presetManualFastBlink() {
  163.   for (int i = 0; i < 5; i++) {
  164.     toggleLaser();
  165.     delay(500);
  166.     toggleLaser();
  167.     delay(500);
  168.   }
  169. }
  170.  
  171. // this is an array containing pointers to each of the automatic presets. A
  172. // function will be called from this list depending on the current preset number
  173. void (*autoPresets[4])() = {presetConstantOff, presetConstantOn,
  174.                             presetAutoSlowBlink, presetAutoFastBlink};
  175.  
  176. // this is an array containing pointers to each of the manual presets. A
  177. // function will be called from this list depending on the button pressed
  178. void (*manualPresets[4])() = {presetConstantOff, presetConstantOn,
  179.                               presetManualSlowBlink, presetManualFastBlink};
  180.  
  181. /******************************************************************************
  182. Rotate Functions: these functions are functions that rotate the servo motor.
  183. They both take a number of degrees to rotate and return nothing.
  184. ******************************************************************************/
  185.  
  186. // this function is used to rotate the servo a certain number of degrees in
  187. // manual mode
  188. void rotateManual(int degrees) {
  189.   // calculate the new position of the servo using the current position of the
  190.   // servo (stored in a variable)
  191.   int newPosition = currentServoRotation + degrees;
  192.  
  193.   // if the new position is greater than 180 (the max the servo can turn in one
  194.   // direction)
  195.   if (newPosition > 180)
  196.     newPosition = 180; // set the calculated new position back to 180
  197.  
  198.   // if the new position is less than 0 (the min the servo can turn in one
  199.   // direction)
  200.   if (newPosition < 0)
  201.     newPosition = 0; // set the calculated new position back to 0
  202.  
  203.   // update the value of the current servo rotation to the new calculated value
  204.   currentServoRotation = newPosition;
  205.  
  206.   servo.write(currentServoRotation); // turn the servo to the given position
  207.   delay(500); // wait for the servo to turn to the given position
  208. }
  209.  
  210. // this function is used to rotate the servo a certain number of degrees in
  211. // manual mode
  212. void rotateAutomatic(int degrees) {
  213.   // the automatic mdoe wokrs by always moving in one direction until it reaches
  214.   // the max position (180), where it resets to the min position (0)
  215.  
  216.   // given how the mode works, if the degrees value is negative, it must be
  217.   // changed to positive
  218.   int fixedDegrees =
  219.       degrees < 0 ? -1 * degrees
  220.                   : degrees; // change the degrees value to positive if it's
  221.                              // negative, and store in a new variable
  222.  
  223.   // calculate the new position using the fixed degrees value
  224.   int newPosition = currentServoRotation + fixedDegrees;
  225.  
  226.   // divide the new calculated position by 180 and take the remainder, then
  227.   // store it in the variable to keep track of the current servo rotation. This
  228.   // ensures that the position never exceeds 180 (the max).
  229.   currentServoRotation = newPosition % 180;
  230.  
  231.   servo.write(currentServoRotation);
  232.   delay(500); // wait for the servo to turn to the given position
  233. }
  234.  
  235. /******************************************************************************
  236. Mode Functions: these functions change the mode of the machine (manual vs
  237. automatic). This affects how the user gives input and interacts with the
  238. machine, and what the machine does given said input.
  239. ******************************************************************************/
  240.  
  241. // this function is the manual mode function. When called repeatedly in the loop
  242. // function (see below), it gives the user full manual control of the machine,
  243. // essentially allowing them to trigger any feature at random. The user can also
  244. // use the play/pause button to switch to automatic mode
  245. void manualMode() {
  246.   // if input is received from the IR remote
  247.   if (IrReceiver.decode()) {
  248.     Serial.println("LOG: Received input from IR remote. Attempting to parse.");
  249.  
  250.     // attempt to parse the data and get a readable integer
  251.     uint32_t decoded = IrReceiver.decodedIRData.decodedRawData;
  252.  
  253.     // this switch statement compares the value of the decoded variable with
  254.     // each of the cases described. In this case, it is checking for the codes
  255.     // of each remote button; if a certain remote button is hit, the machine
  256.     // will perform the associated operation. Tests were performed beforehand to
  257.     // find the code for each key on the remote.
  258.     switch (decoded) {
  259.     case 4077715200: // if the 1 key is pressed on the remote
  260.       Serial.println("LOG: Remote input is button 1.");
  261.       (*manualPresets[0])(); // call the first manual preset function declared
  262.                              // in the array above
  263.       break;
  264.     case 3877175040: // if the 2 key is pressed on the remote
  265.       Serial.println("LOG: Remote input is button 2.");
  266.       (*manualPresets[1])(); // call the second manual preset function declared
  267.                              // in the array above
  268.       break;
  269.     case 2707357440: // if the 3 key is pressed on the remote
  270.       Serial.println("LOG: Remote input is button 3.");
  271.       (*manualPresets[2])(); // call the third manual preset function declared
  272.                              // in the array above
  273.       break;
  274.     case 4144561920: // if the 4 key is pressed on the remote
  275.       Serial.println("LOG: Remote input is button 4.");
  276.       (*manualPresets[3])(); // call the fourth manual preset function declared
  277.                              // in the array above
  278.       break;
  279.     case 3141861120: // if the back key is pressed on the remote
  280.       Serial.println("LOG: Remote input is button BACK.");
  281.       rotateManual(
  282.           -30); // rotate the motor 30 degrees counter clockwise (if possible)
  283.       break;
  284.     case 3158572800: // if the forward key is pressed on the remote
  285.       Serial.println("LOG: Remote input is button FORWARD.");
  286.       rotateManual(30); // rotate the motor 30 degrees clockwise (if possible)
  287.       break;
  288.     case 3208707840: // if the play/pause key is pressed on the remote
  289.       Serial.println("LOG: Remote input is button PLAY/PAUSE.");
  290.       Serial.println("LOG: Switching to AUTO mode.");
  291.       automatic = true; // set the global automatic flag to true (change to
  292.                         // automatic mode)
  293.       writeText(WORD_AUTOMATIC,
  294.                 WORD_AUTOMATIC_SIZE); // print AUTOMATIC on the LCD
  295.       // tone(BUZZER_PIN, 300, 1000); // play the buzzer sound to give the user
  296.       // an audio cue for mode change
  297.       break;
  298.     }
  299.  
  300.     IrReceiver.resume(); // continue collecting input
  301.   }
  302.  
  303.   return;
  304. }
  305.  
  306. // this function is the automatic mode function. When called repeatedly in the
  307. // loop function (see below), the function causes the machine to run
  308. // automatically and randomly, in that it will continuously call random presets.
  309. // this function will also give the user the ability to increase or decrease the
  310. // speed of rotation using the up/down buttons on the remote. The user will
  311. // also be able to switch to manual mode at any time using the play/pause button
  312. // on the remote.
  313. void automaticMode() {
  314.   rotateAutomatic(
  315.       automaticRotationSpeed); // rotate at the speed declared by the
  316.                                // automaticRotationSpeed global variable
  317.  
  318.   unsigned long currentTime =
  319.       millis(); // collect the current time to run comparisons against the two
  320.                 // async clocks
  321.  
  322.   // if the current time collected is greater than 5 seconds later than clock1
  323.   // (the last recorded checkpoint)
  324.   if (currentTime > clock1 + 5000) {
  325.     Serial.print("LOG: Selecting new random preset. New preset: ");
  326.     currentPreset =
  327.         random(4); // select a new random number from 0 to 3 inclusive and set
  328.                    // it as the global currentPreset variable
  329.     Serial.println(currentPreset);
  330.     clock1 = currentTime; // update clock1 to represent now as the last marked
  331.                           // time (checkpoint)
  332.   }
  333.  
  334.   // if input is received from the IR remote
  335.   if (IrReceiver.decode()) {
  336.     Serial.println("LOG: Received input from IR remote. Attempting to parse.");
  337.  
  338.     // attempt to parse the data and get a readable integer
  339.     uint32_t decoded = IrReceiver.decodedIRData.decodedRawData;
  340.  
  341.     // this switch statement compares the value of the decoded variable with
  342.     // each of the cases described. In this case, it is checking for the codes
  343.     // of each remote button; if a certain remote button is hit, the machine
  344.     // will perform the associated operation. Tests were performed beforehand to
  345.     // find the code for each key on the remote.
  346.     switch (decoded) {
  347.     case 3208707840: // if the play/pause key is pressed on the remote
  348.       Serial.println("LOG: Remote input is button PLAY/PAUSE.");
  349.       Serial.println("LOG: Switching to MANUAL mode.");
  350.       automatic = false; // set the global automatic flag to false (change to
  351.                          // manual mode)
  352.       writeText(WORD_MANUAL, WORD_MANUAL_SIZE); // print MANUAL on the LCD
  353.       // tone(BUZZER_PIN, 300, 1000); // play the buzzer sound to give the user
  354.       // an audio cue for mode change
  355.       break;
  356.     case 4127850240: // if the up key is pressed on the remote
  357.       Serial.println("LOG: Increasing automatic speed by 5.");
  358.       automaticRotationSpeed +=
  359.           5; // increase the current speed for automatic mode rotation by 5
  360.       if (automaticRotationSpeed >
  361.           45) { // if the automatic rotation speed is above 45 (max)
  362.         Serial.println("WARN: Automatic speed is above 45. Resetting to 45.");
  363.         automaticRotationSpeed =
  364.             45; // set the automatic rotation speed back to 45
  365.       }
  366.       break;
  367.     case 4161273600: // if the down key is pressed on the remote
  368.       Serial.println("LOG: Decreasing automatic speed by 5.");
  369.       automaticRotationSpeed -=
  370.           5; // decrease the current speed for automatic mode rotation by 5
  371.       if (automaticRotationSpeed <
  372.           0) { // if the automatic rotation speed is below 45 (min)
  373.         Serial.println("WARN: Automatic speed is below 0. Resetting to 0.");
  374.         automaticRotationSpeed =
  375.             0; // set the automatic rotation speed back to 0
  376.       }
  377.       break;
  378.     }
  379.  
  380.     IrReceiver.resume(); // continue collecting input
  381.   }
  382.  
  383.   (*autoPresets[currentPreset])(); // call the current preset function (as
  384.                                    // determined by the global var
  385.                                    // currentPreset)
  386. }
  387.  
  388. /******************************************************************************
  389. Setup function: this function is automatically called once, and has a return
  390. type of void.
  391. ******************************************************************************/
  392.  
  393. void setup() {
  394.   Serial.begin(9600); // intialize the Serial monitor
  395.  
  396.   Serial.println("LOG: Starting init sequence.");
  397.  
  398.   // initialize the LCD
  399.   Serial.println("LOG: Initializing LCD.");
  400.   lcd.init();
  401.   lcd.backlight(); // turn on the LCD backlight
  402.  
  403.   // initialize the IR Remote and bind it to the correct pin
  404.   Serial.println("LOG: Initializing IR Remote.");
  405.   IrReceiver.begin(IR_RECEIVE_PIN);
  406.  
  407.   // initialize the random seed (needed to use random functionality in automatic
  408.   // function)
  409.   Serial.println("LOG: Planting randomizer seed using empty analog input 0.");
  410.   randomSeed(analogRead(0));
  411.  
  412.   // intialize clocks to current time to enable asynchronous functionality
  413.   Serial.println("LOG: Initializing clocks.");
  414.   clock1 = millis();
  415.   clock2 = millis();
  416.  
  417.   // intialize currentPreset and randomInterval with random values from the
  418.   // random seed
  419.   Serial.println("LOG: Setting required random values.");
  420.   randomInterval =
  421.       random(5000, 15001);   // the random interval will always be a number from
  422.                              // 5000 ms to 15000 ms (5-15 seconds)
  423.   currentPreset = random(4); // the currentPreset will always be a number from
  424.                              // 0-3 because there are four presets
  425.  
  426.   // set up laser
  427.   Serial.println("LOG: Setting up laser.");
  428.   pinMode(LASER_PIN, OUTPUT);   // set the laser to OUTPUT mode
  429.   digitalWrite(LASER_PIN, LOW); // turn off the laser to begin
  430.  
  431.   // set up servo
  432.   Serial.println("LOG: Setting up servo.");
  433.   servo.attach(SERVO_PIN); // attach the servo object to the correct pin
  434.   servo.write(currentServoRotation); // turn the servo to the correct position
  435.                                      // (initially 0)
  436.  
  437.   Serial.println("LOG: Starting in AUTO mode.");
  438.   writeText(WORD_AUTOMATIC,
  439.             WORD_AUTOMATIC_SIZE); // print AUTOMATIC on the LCD (because it is
  440.                                   // the starting mode)
  441.  
  442.   Serial.println("LOG: Playing initialization tone.");
  443.   // tone(BUZZER_PIN, 300, 1000); // play a tone to notify the user the machine
  444.   // has been initialized
  445. }
  446.  
  447. /******************************************************************************
  448. Loop function: this function is called repeatedly for the lifespan of the
  449. program and has a return type of void.
  450. ******************************************************************************/
  451.  
  452. void loop() {
  453.   // if the current mode is automatic (as determined by the automatic global
  454.   // boolean), then call the automaticMode function. Else, call the manualMode
  455.   // function. Since this check is in the loop function, this check will
  456.   // continue to be run forever, meaning that the correct function (be it
  457.   // automatic or manual) will be called many times in quick succession. The
  458.   // program depends heavily on this mechanism to function.
  459.   if (automatic) {
  460.     automaticMode();
  461.   } else {
  462.     manualMode();
  463.   }
  464. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement