Can someome PLEASE help with an issue on an arduino based safe box I have made? Very time sensitive!

My main question is this (explanation below:)
Is the encoder I am using not designed to work in this type of function? Could the encoder be the cause of all of these issues described below?

I could really use some help please with this project that I am building. It is extremely time sensitive. I am planning to put my resume inside on a USB stick and mail it to a company that I am trying to get my dream marketing job with. I contacted the person who created this safe box but he hasn’t responded.

Here is a link to the project:

Here is the problem:
When I connect a brand new 9V battery and turn it on the display shows the text “Crack the Code”, 1 green LED flashes a bit, and the servo is moving in short increments up several times and then down. It’s like it’s stuck in a loop. I was also using a mini-toggle that was a SPDT on-on, instead of a SPST toggle, which shouldn’t matter. Even if I remove the switch and connect the power to the battery directly I still have the same exact issues.

If I remove the 9V and plug in the USB power to the arduino then the code loads to the “Press to Start” screen and has all of the LEDs lit up, which is as it should be. If I then press on the encoder to start the game the servo moves correctly to the “lock” position, the LEDs turn off, and the screen goes to “0000.” That is all as it should be.

If I rotate the encoder nothing happens and if I press the encoder button again nothing happens. It’s like it’s frozen.

I am using a different encoder than the one linked to. The one in the BOM is this:

The encoder that I am using is this one (it’s the one I had on hand:)

Bourns 652-PEC11R-4220F-S24
https://www.mouser.com/ProductDetail/652-PEC11R-4220F-S24

I am using the code as it is with no changes.

Anyone have any thoughts or ideas as to what is wrong? Any help would be so, so greatly appreciated!

I’m sorry but I’ve not got tons of time to help right now, however I recall having an issue with the rotary encoders that come without a PCB or pull up resistors. I had one like this:
PEC11R-4220K-S0024 | Bourns 24 Pulse Incremental Mechanical Rotary Encoder with a 6 mm Knurl Shaft, Through Hole | RS Components (rs-online.com)
and replacing it with this:

Keyes Rotary Encoder Module For Arduino (hobbyking.com)
fixed the problem. (search keyes rotary encoder).
You might need to change the code though.

2 Likes

Hm… I think you got two problems there, not sure if they are related. I would suggest to work on the second issue first, the one that is not about the power.
You could add some debugging prints into the code to see at which point exactly the code hangs. Is it just waiting for input or did something really crash the arduino?
Also you should try if you can use the encoder with the encoder example sketch.

2 Likes

Thanks. I’m reading up right now on how to run the encoder sketch with this. I’m not an arduino programmer.

Is it possible that the encoder issue could be also causing the power issue?

I’ve also ordered the encoder listed in the BOM and it should be arriving today.

1 Like

You can try the code below, then open the arduino serial monitor (Tools → Serial Monitor) and check if you get something when you turn the encoder. )I adjusted the pins in the code below to match what is written in your guide, so the encoder should be connected to digital pins 2 and 3.

I don’t think the encoder causes the power issue, but I can’t say for sure. you could try another sketch that does not use the encoder, but just moves the servo.

/* Encoder Library - Basic Example
 * http://www.pjrc.com/teensy/td_libs_Encoder.html
 *
 * This example code is in the public domain.
 */

#include <Encoder.h>

// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder myEnc(2, 3);
//   avoid using pins with LEDs attached

void setup() {
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
}

long oldPosition  = -999;

void loop() {
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    Serial.println(newPosition);
  }
}
2 Likes

Here is some code that tests the servo, you can see if that works when using battery:

/* Sweep
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep
*/

#include <Servo.h>

Servo myservo;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position

void setup() {
  myservo.attach(5);  // attaches the servo on pin 9 to the servo object
}

void loop() {
  for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15ms for the servo to reach the position
  }
  for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15ms for the servo to reach the position
  }
}

1 Like

So the encoder shows nothing in the serial monitor when you turn or press it. That would mean then that the encoder is an issue and swapping it out may fix that, yes?

When the battery is installed the servo moves in increments all the way up but then when it gets up it begins to move back and forth smoothly and continuously like it did when it was plugged into the USB.

So what does that mean exactly? Power is getting to the servo then and the encoder could possibly be the issue as well or no?

Right now I have made the change to the code so that the servo goes to the locked position when it is turned on. (This is code that the guy who created this suggested on the project page.) I am trying to get this to work so that when they receive the box the servo is already in the locked position. This way they have to turn it on and enter the code before they can open it. Otherwise they could just open it without turning it on.

This is the code I currently have:

//Code Breaker
//Michael Klements
//The DIY Life
//15 May 2020

//Encoder interrupt routine adapted from Simon Merrett's example code

#include <SPI.h>                          //Import libraries to control the OLED display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Servo.h>                        //Import library to control the servo

Servo lockServo;                          //Create a servo object for the lock servo

#define SCREEN_WIDTH 128                  // OLED display width, in pixels
#define SCREEN_HEIGHT 32                  // OLED display height, in pixels

#define OLED_RESET -1                     // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);   // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)

static int pinA = 2;                      //Hardware interrupt digital pin 2
static int pinB = 3;                      //Hardware interrupt digital pin 3
volatile byte aFlag = 0;                  //Rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0;                  //Rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile byte encoderPos = 0;             //Current value of encoder position, digit being input form 0 to 9
volatile byte prevEncoderPos = 0;         //To track whether the encoder has been turned and the display needs to update
volatile byte reading = 0;                //Stores direct value from interrupt pin

const byte buttonPin = 4;                 //Pin number for encoder push button
byte oldButtonState = HIGH;               //First button state is open because of pull-up resistor
const unsigned long debounceTime = 10;    //Debounce delay time
unsigned long buttonPressTime;            //Time button has been pressed for debounce

byte correctNumLEDs[4] = {9,12,7,11};      //Pin numbers for correct number LEDs (Indicate a correct digit)
byte correctPlaceLEDs[4] = {6,10,8,13};    //Pin numbers for correct place LEDs (Indicate a correct digit in the correct place)

byte code[4] = {0,0,0,0};                  //Create an array to store the code digits
byte codeGuess[4] = {0,0,0,0};             //Create an array to store the guessed code digits
byte guessingDigit = 0;                    //Tracks the current digit being guessed
byte numGuesses = 0;                       //Tracks how many guesses it takes to crack the code
boolean correctGuess = true;               //Variable to check whether the code has been guessed correctly, true initially to generate a new code on startup

void setup()
{
  Serial.begin(9600);                                 //Starts the Serial monitor for debugging
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))      //Connect to the OLED display
  {
    Serial.println(F("SSD1306 allocation failed"));   //If connection fails
    for(;;);                                          //Don't proceed, loop forever
  }
  display.clearDisplay();                             //Clear display
  lockServo.attach(5);                                //Assign the lock servo to pin 5
  for(int i=0 ; i<=3 ; i++)                           //Define pin modes for the LEDs
  {
    pinMode(correctNumLEDs[i], OUTPUT);
    pinMode(correctPlaceLEDs[i], OUTPUT);
  }
  pinMode(pinA, INPUT_PULLUP);                        //Set pinA as an input, pulled HIGH to the logic voltage
  pinMode(pinB, INPUT_PULLUP);                        //Set pinB as an input, pulled HIGH to the logic voltage
  attachInterrupt(0,PinA,RISING);                     //Set an interrupt on PinA
  attachInterrupt(1,PinB,RISING);                     //Set an interrupt on PinB
  pinMode (buttonPin, INPUT_PULLUP);                  //Set the encoder button as an input, pulled HIGH to the logic voltage
  randomSeed(analogRead(0));                          //Randomly choose a starting point for the random function, otherwise code pattern is predictable
  display.setTextColor(SSD1306_WHITE);                //Set the text colour to blue
  startupAni();                                       //Display the startup animation
  lockServo.write(45);                                //Locks the safe when turned on
}

void loop() 
{
  if(correctGuess)                                            //Code between games to reset if the guess is correct, initially true to open safe and then generate new code
  {
    delay(300);
    updateLEDs (0,4);                                         //Flashing LED sequence
    delay(300);
    updateLEDs (4,0);
    delay(300);
    updateLEDs (0,4);
    delay(300);
    updateLEDs (4,0);
    delay(300);
    updateLEDs (4,4);                                         //Turn all LEDs on
    if(numGuesses >= 1)                                       //Check that its not the start of the game
    {
      lockServo.write(140);                                   //Unlock the safe
      display.clearDisplay();                                 //Clear the display
      display.setTextSize(1);                                 //Set the display text size to small
      display.setCursor(35,10);                               //Set the display cursor position
      display.print(F("In "));                                //Set the display text
      display.print(numGuesses);                              //Set the display text
      display.setCursor(35,20);                               //Set the display cursor position
      display.print(F("Attempts"));                           //Set the display text
      display.display();                                      //Output the display text
      delay(5000);
    }
    display.clearDisplay();                                   //Clear the display
    display.setTextSize(1);                                   //Set the display text size to small
    display.setCursor(35,10);                                 //Set the display cursor position
    display.print(F("Push to"));                              //Set the display text
    display.setCursor(35,20);                                 //Set the display cursor position
    display.print(F("Start"));                            //Set the display text
    display.display();                                        //Output the display text
    display.setTextSize(2);                                   //Set the display text size back to large
    boolean lock = false;                                     //Safe is initially not locked
    boolean pressed = false;                                  //Keeps track of button press
    while(!lock)                                              //While button is not pressed, wait for it to be pressed
    {
      byte buttonState = digitalRead (buttonPin); 
      if (buttonState != oldButtonState)
      {
        if (millis () - buttonPressTime >= debounceTime)      //Debounce button
        {
          buttonPressTime = millis ();                        //Time when button is pressed
          oldButtonState =  buttonState;                      //Remember button state
          if (buttonState == LOW)
          {
            pressed = true;                                   //Records button has been pressed
          }
          else 
          {
            if (pressed == true)                              //Makes sure that button is pressed and then released before continuing in the code
            {
              lockServo.write(45);                            //Lock the safe
              display.clearDisplay();                         //Clear the display
              display.setCursor(30,10);                       //Set the display cursor position
              display.print(F("Locked"));                     //Set the display text
              display.display();                              //Output the display text
              lock = true;
            }
          }  
        }
      }
    }
    generateNewCode();                                        //Calls function to generate a new random code
    updateLEDs (0,0);
    correctGuess = false;                                     //The code guess is initially set to incorrect
    numGuesses = 0;                                           //Reset the number of guesses counter
  }
  inputCodeGuess();                                           //Calls function to allow the user to input a guess
  numGuesses++;                                               //Increment the guess counter
  checkCodeGuess();                                           //Calls function to check the input guess
  encoderPos = 0;                                             //Reset the encoder position
  guessingDigit = 0;                                          //Reset the digit being guessed
  codeGuess[0] = 0;                                           //Reset the first digit of the code
  updateDisplayCode();                                        //Update the displayed code
}

void updateDisplayCode()                                      //Function to update the display with the input code
{
  String temp = "";                                           //Temporary variable to concatenate the code string
  if(!correctGuess)                                           //If the guess is not correct then update the display
  {
    for (int i=0 ; i<guessingDigit ; i++)                     //Loops through the four digits to display them
    {
      temp = temp + codeGuess[i];
    }
    temp = temp + encoderPos;
    for (int i=guessingDigit+1 ; i<=3 ; i++)
    {
      temp = temp + "0";
    }
    Serial.println(temp);                                     //Output to Serial monitor for debugging
    display.setTextSize(2);                                   //Set the display text size
    display.clearDisplay();                                   //Clear the display
    display.setCursor(40,10);                                 //Set the display cursor position
    display.println(temp);                                    //Set the display text
    display.display();                                        //Update the display
  }
}

void generateNewCode()                                        //Function to generate a new random code
{
  Serial.print("Code: ");
  for (int i=0 ; i<= 3 ; i++)                                 //Loops through the four digits and assigns a random number to each
  {
    code[i] = random(0,9);                                    //Generate a random number for each digit
    Serial.print(code[i]);                                    //Display the code on Serial monitor for debugging
  }
  Serial.println();
}

void inputCodeGuess()                                         //Function to allow the user to input a guess
{
  for(int i=0 ; i<=3 ; i++)                                   //User must guess all four digits
  {
    guessingDigit = i;
    boolean confirmed = false;                                //Both used to confirm button push to assign a digit to the guess code
    boolean pressed = false;
    encoderPos = 0;                                           //Encoder starts from 0 for each digit
    while(!confirmed)                                         //While the user has not confirmed the digit input
    {
      byte buttonState = digitalRead (buttonPin); 
      if (buttonState != oldButtonState)
      {
        if (millis () - buttonPressTime >= debounceTime)      //Debounce button
        {
          buttonPressTime = millis ();                        //Time when button was pushed
          oldButtonState =  buttonState;                      //Remember button state for next time
          if (buttonState == LOW)
          {
            codeGuess[i] = encoderPos;                        //If the button is pressed, accept the current digit into the guessed code
            pressed = true;
          }
          else 
          {
            if (pressed == true)                              //Confirm the input once the button is released again
            {
              updateDisplayCode();                            //Update the code being displayed
              confirmed = true;
            }
          }  
        }
      }
      if(encoderPos!=prevEncoderPos)                          //Update the displayed code if the encoder position has changed
      {
        updateDisplayCode();
        prevEncoderPos=encoderPos;
      }
    }
  }
}

void checkCodeGuess()                                         //Function to check the users guess against the generated code
{
  int correctNum = 0;                                         //Variable for the number of correct digits in the wrong place
  int correctPlace = 0;                                       //Variable for the number of correct digits in the correct place
  int usedDigits[4] = {0,0,0,0};                              //Mark off digits which have been already identified in the wrong place, avoids counting repeated digits twice
  for (int i=0 ; i<= 3 ; i++)                                 //Loop through the four digits in the guessed code
  {
    for (int j=0 ; j<=3 ; j++)                                //Loop through the four digits in the generated code
    {
      if (codeGuess[i]==code[j])                              //If a number is found to match
      {
        if(usedDigits[j]!=1)                                  //Check that it hasn't been previously identified
        {
          correctNum++;                                       //Increment the correct digits in the wrong place counter
          usedDigits[j] = 1;                                  //Mark off the digit as been identified
          break;                                              //Stop looking once the digit is found
        }
      }
    }
  }
  for (int i=0 ; i<= 3 ; i++)                                 //Compares the guess digits to the code digits for correct digits in correct place
  {
    if (codeGuess[i]==code[i])                                //If a correct digit in the correct place is found
      correctPlace++;                                         //Increment the correct place counter
  }
  updateLEDs(correctNum, correctPlace);                        //Calls a function to update the LEDs to reflect the guess
  if(correctPlace==4)                                          //If all 4 digits are correct then the code has been cracked
  {
    display.clearDisplay();                                    //Clear the display
    display.setCursor(20,10);                                  //Set the display cursor position
    display.print(F("Cracked"));                               //Set the display text
    display.display();                                         //Output the display text
    correctGuess = true;
  }
  else
    correctGuess = false;
}

void updateLEDs (int corNum, int corPla)                        //Function to update the LEDs to reflect the guess
{
  for(int i=0 ; i<=3 ; i++)                                     //First turn all LEDs off
  {
    digitalWrite(correctNumLEDs[i], LOW);
    digitalWrite(correctPlaceLEDs[i], LOW);
  }
  for(int j=0 ; j<=corNum-1 ; j++)                              //Turn on the number of correct digits in wrong place LEDs
  {
    digitalWrite(correctNumLEDs[j], HIGH);
  }
  for(int k=0 ; k<=corPla-1 ; k++)                              //Turn on the number of correct digits in the correct place LEDs
  {
    digitalWrite(correctPlaceLEDs[k], HIGH);
  }
}

void startupAni ()
{
  display.setTextSize(2);                     //Set the display text size
  display.setCursor(30,10);                   //Set the display cursor position
  display.println(F("Charlie"));                //Set the display text
  display.display();                          //Output the display text
  delay(700);
  display.clearDisplay();                     //Clear the display
  display.setCursor(45,10);                   //Set the display cursor position
  display.println(F("and"));                //Set the display text
  display.display();                          //Output the display text
  delay(700);
  display.clearDisplay();                     //Clear the display
  display.setCursor(35,10);                   //Set the display cursor position
  display.println(F("Mike"));                //Set the display text
  display.display();                          //Output the display text
  delay(700);
  display.clearDisplay();                     //Clear the display
  display.setCursor(35,10);                   //Set the display cursor position
  display.println(F("Crack"));                //Set the display text
  display.display();                          //Output the display text
  delay(700);
  display.clearDisplay();                     //Clear the display
  display.setCursor(45,10);
  display.println(F("the"));
  display.display();
  delay(700);
  display.clearDisplay();
  display.setCursor(40,10);
  display.println(F("Code"));
  display.display();
  delay(700);
  display.clearDisplay();
}

void PinA()                               //Rotary encoder interrupt service routine for one encoder pin
{
  cli();                                  //Stop interrupts happening before we read pin values
  reading = PIND & 0xC;                   //Read all eight pin values then strip away all but pinA and pinB's values
  if(reading == B00001100 && aFlag)       //Check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
  {     
    if(encoderPos>0)
      encoderPos --;                      //Decrement the encoder's position count
    else
      encoderPos = 9;                     //Go back to 9 after 0
    bFlag = 0;                            //Reset flags for the next turn
    aFlag = 0;                            //Reset flags for the next turn
  }
  else if (reading == B00000100)          //Signal that we're expecting pinB to signal the transition to detent from free rotation
    bFlag = 1;
  sei();                                  //Restart interrupts
}

void PinB()                               //Rotary encoder interrupt service routine for the other encoder pin
{
  cli();                                  //Stop interrupts happening before we read pin values
  reading = PIND & 0xC;                   //Read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00001100 && bFlag)      //Check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
  {
    if(encoderPos<9)
      encoderPos ++;                      //Increment the encoder's position count
    else
      encoderPos = 0;                     //Go back to 0 after 9
    bFlag = 0;                            //Reset flags for the next turn
    aFlag = 0;                            //Reset flags for the next turn
  }
  else if (reading == B00001000)          //Signal that we're expecting pinA to signal the transition to detent from free rotation
    aFlag = 1;
  sei();                                  //Restart interrupts
}


The encoder type you used seems fine, but it might of course by faulty. If you have another one, swap them out.

The power supply issues are quite odd. I’d always advice against powering a servo through the onboard regulator of the Arduino. It’d be better to use an external regulator, or use a 5V supply (cheap powerbank maybe?). Can you monitor the voltage on the 5V pin while operating from the battery?

1 Like

Hm, yeah, you could try the other encoder, but I don’t know if they really can be so different. But it’s always worth a try! I have seen different examples of connecting the center pin to ground or to 5V , but I guess it’s a matter of programming and in the example and in your code it should be connected to ground.

For the servo: in the example code it should move back and forth from 0 to 180 degree in tiny steps, so relatively smooth… do you notice a difference between battery and usb here?
I could imagine that there is not enough power for the servo with the battery, so it does strange things (maybe it also depends on if the leds are on), but I am out of my depths here, maybe our Arduino wizards (@fredrik ? ) can help?

Also, if you have a spare Arduino, try swapping that, I had some really strange issues recently (Arduino getting really hot when powered externally, analogue Pins not reading correct values,…) and it turned out to work with a different Arduino!

1 Like

When the servo moves on battery power it isn’t smooth. It moves in maybe 20 degree increments and stops, 20 degrees, stops, 20 degrees, stops, etc… On USB power when it first turns on it does this same 20 degree increment/stop thing for an 180 degree rotation and then it moves smoothly back 180 degrees to where it initially started and then stops.

I am going to swap out the encoder right now with the new one that came today and see what happens…be back in a few minutes

Does it do that with this code?

Cause if it does, it’s time to swap out the Arduino. Like Sebastian said, they do come faulty sometimes. That behaviour you described makes no sense with this sketch.

2 Likes

@TimMJN I will run that code and see, thank you! I got the encoder on last night but it didnt change the behavior. Still having the same issues. I literally just used my last Uno to create an arduino as ISP device to program a bunch of AtMega chips. I may need to run to the store tomorrow and pick one up if thats the case.

@TimMJN and @sebastian … I had to also swap out the screen. I think I may have fried something in it during testing. I triple checked all of the wires to the schematic. Everything is correctly connected. I ran the code that you gave me above Tim and on USB power everything works as expected. The encoder works fine, screen fine, game plays as it should. The only thing that I can’t figure out how to fix on that part of it is how to also keep the servo in the locked position when it is turned off. I don’t know if there’s a way to code that? I don’t know if it’s possible with arduino programming but maybe something where you press and hold the encoder for X number of seconds while it is turned on it will send it to the locked position and remain there even if turned off OR until you solve the game. Then if it powers back on and the servo is in the locked position it remains there or if it is in the unlocked position when turned on it moves to the locked position.

That being said…I still have an issue with battery power. It still seems to get stuck in a loop. It takes a bit to load the code and gets stuck after the first set of text is shown “Charlie and Mike crack the code.” Doesn’t matter if you press the encoder or not Nothing happens and the servo does those increments. I checked on both USB and battery power the 5v pin and I am receiving 5v both on battery power and USB power.

Any ideas on how to fix this? Would adding a 2nd 9V do anything?

Thank you to all for the help! Greatly appreciate it. :slight_smile:

2 Likes

Glad to hear that the encoder works! For the power: that’s still strange, but since you don’t have so much time, as you mentioned, I would suggest to just get a small powerbank and use that instead of the 9V block for now…

For the start in locked position: that should be an easy change in the code, but I am too tired for that at the moment (my 8month old daughter sleeps poorly and keeps me awake since a few weeks :wink: ).

1 Like

This should already work…

Except that regardless if it is in the “locked” position when you turn it off when you turn it back on it will move for a brief time to “unlocked” and then back to “locked.” So if you were really wanting to see what was inside and didn’t want to play the game you could easily just shut off the power, turn it back on and open it during the few seconds the servo is in the unlocked position. :pensive:

I don’t know how to fix this part of it.

I understand completely. I have a son who will be 2 next month. He was a terrible sleeper and eater for the longest time. He still doesn’t sleep through the night entirely. It’s one of the reasons why I have always stayed awake from 11pm - 4am everyday. I still get up at 8:30 or 9am each morning regardless of the time I go to sleep.

I don’t understand the power thing either :frowning:

1 Like

What if you move this line

lockServo.write(45); 

Right to the start? Before the serial.begin() line?