Components and supplies
470 Ohms Resistor (or multiple to add 470 Ohms)
Arduino Nano
Relay (generic)
rotary encoder
LED 5mm red
Transistor PNP
Piezo Buzzer
Push Button
Tools and machines
Soldering kit
Apps and platforms
Arduino IDE
Project description
Code
code
cpp
...
1/* ENCODER OPERATED COMBINATION LOCK V 3.2 2 3 Copyright 2019 by Doug Pirkey 4 5 This work is licensed under the Creative Commons Attribution 4.0 International License. 6 To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/. 7 8 9 A simulation of a mechanical dial combination lock to run on an Arduino UNO 10 11 Program Demonstrates: 12 EEPROM save/restore 13 Pin change interrupts 14 Sleep mode 15 Self-made timer library 16 memset(), memcmp(), memcpy() 17 Rotary encoder w external interrupts 18 #defines for compile time options 19 Switch/case state machine 20 21 Number of possible combinations is given by n^r: 22 n = number of different numbers (40) 23 r = number of tumblers used (5) 24 25 So the current configuration yields 102,400,000 possible combinations, 26 correctly called permutations. 27 28 Number lock permutations calculator 29 https://www.easycalculation.com/statistics/number-lock-permutations.php 30*/ 31//----------------------------------------------- 32// EEPROM.h is part of the Arduino library. 33 34// https://www.arduino.cc/en/Reference/EEPROM/ 35 36// https://creativecommons.org/licenses/by-sa/3.0/ 37 38#include <EEPROM.h> // For storage/retrieval of lock code 39 40//----------------------------------------------- 41// Change these two numbers to the pins connected to your encoder. 42// Best Performance: both pins have interrupt capability 43// Good Performance: only the first pin has interrupt capability 44// Low Performance: neither pin has interrupt capability 45// Refer to the link for the rotary encoder license 46 47// PaulStoffregen's GitHub page for the encoder library - https://github.com/PaulStoffregen/Encoder 48// license description - https://github.com/PaulStoffregen/Encoder/blob/master/Encoder.h 49#include <Encoder.h> 50Encoder ComboDial(2, 3); 51 52//--------------------------------------------- 53 54#include "Multi_Timer.h" // self-made timer library 55 56//---------------------------------------------- 57 58/* Sleep library is copyright Microchip. 59 60 https://www.nongnu.org/avr-libc/user-manual/sleep_8h_source.html. 61 62 Copyright (c) 2002, 2004 Theodore A. Roth 63 Copyright (c) 2004, 2007, 2008 Eric B. Weddington 64 Copyright (c) 2005, 2006, 2007 Joerg Wunsch 65 All rights reserved. 66 67 Redistribution and use in source and binary forms, with or without 68 modification, are permitted provided that the following conditions are met: 69 70 Redistributions of source code must retain the above copyright 71 notice, this list of conditions and the following disclaimer. 72 73 Redistributions in binary form must reproduce the above copyright 74 notice, this list of conditions and the following disclaimer in 75 the documentation and/or other materials provided with the 76 distribution. 77 78 Neither the name of the copyright holders nor the names of 79 contributors may be used to endorse or promote products derived 80 from this software without specific prior written permission. 81 82 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 83 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 84 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 85 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 86 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 87 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 88 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 89 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 90 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 91 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 92 POSSIBILITY OF SUCH DAMAGE. 93*/ 94#include <avr/sleep.h> 95// 96// **** Defines to control runtime features **** 97// 98#define POWER_SAVE // enables processor power down feature 99// 100#define VERIFY_NEW_COMBO // enables new combo verification code in state machine 101// 102#define DISPLAY_LOCK_CODE // print stored code and entered code on serial monitor 103// 104// ================ Constants ============== 105 106const uint8_t INTPIN1 = 2; // Rotary encoder interrupt on this Arduino Uno pin. 107const uint8_t INTPIN2 = 3; // Rotary encoder interrupt on this Arduino Uno pin. 108const uint8_t ENCODER_PB = 5; // Encoder pushbutton input 109const uint8_t lockSolenoid = 7; // +5--/\/\/- 330Ω -->|--DIO7 110const uint8_t SET_TO_DEFAULT = 8; // Pin held low at power-up resets to default combo - 221 111const uint8_t modeLED = 9; // +5--/\/\/- 330Ω -->|--DIO9 112const uint8_t detentFlashLED = 12; // +5--/\/\/- 330Ω -->|--DI12 113 114const uint8_t MAX_TUMBLERS = 5; // number of possible code entries 115const uint8_t MAX_TUMBLER_CODE = 40; // max number of dial turns per code entry. 116const unsigned int EEPROM_BASE = 20; // starting EEPROM address for code storage 117 118const bool CW = true; // Encoder direction 119const bool CCW = !CW; // booleans 120 121const bool PB_PULSE = true; // readEncoderPB argument 122const bool PB_STATE = false; // readEncoderPB argument 123 124// ================= Variables ==================== 125 126uint8_t tumblerData[MAX_TUMBLERS]; // holds numbers entered for opening or combo setting 127uint8_t lockCode[] {2, 2, 1, 0, 0}; // holds working combination to open lock. 128// 221 is default for reset/troubleshooting 129uint8_t newCode[MAX_TUMBLERS]; // temp storage of changed code 130int8_t workingTumbler = -1; // which code number is being entered 131bool isEncoderDirectionCW; // clockwise/counterclockwise indicator 132bool isEncoderPBPressed; 133bool telltale = false; // debugging aid 134bool isChangeComboFlag; 135bool codeError; // Too many numbers entered 136 137unsigned long lastDebounceTime = 0; // the last time the output pin was toggled 138unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers 139 140bool encoderTurned; // Signals encoder activity 141 142// ============= Define state machine names ============ 143enum : byte { 144 FIRST_SCAN, 145 INIT_ENTER, 146 ENTER_COMBO, 147 INIT_OPEN, 148 OPEN_LOCK, 149 INIT_NEW, 150 NEW_COMBO, 151 INIT_VERIFY, 152 VERIFY_COMBO, 153} lockState; 154 155// Timers - from Multi_timer.h 156OnDelay_tmr detentFlash_timer(20); // Blinks for visual indication of encoder movement 157// could be a buzzer instead, if desired. 158// or deleted entirely. 159OnDelay_tmr solenoidON_timer(3000); // Actuate mechanical lock release 160Flasher_tmr modeFlasher_timer(1000, 200); // 1 sec. period, 200ms on time 161OnDelay_tmr NewCombo_timer(1000); // Enter set mode when this times out 162WatchDog_tmr activity_timer(15000); // Reset to idle or enter sleep mode if nothing happening on the encoder 163 164 165// === S E T U P === 166//---------------------------------------------------------- 167void setup() { 168 Serial.begin(115200); 169 Serial.println(__FILE__); 170 pinMode(SET_TO_DEFAULT, INPUT_PULLUP); 171 if (digitalRead(SET_TO_DEFAULT) == 0) { // Optional load of default code at startup. 172 EEPROM.put(EEPROM_BASE, lockCode); 173 Serial.println("Default code set!"); 174 } 175 EEPROM.get(EEPROM_BASE, lockCode); // retrieve current lock code to RAM 176 177 pinMode(ENCODER_PB, INPUT_PULLUP); 178 PCICR |= (1 << PCIE2); // enable interrupts on port D - for wakeup 179 180 // assign LEDs - use onboard pin 13 for dial status indicator 181 pinMode(detentFlashLED, OUTPUT); 182 // external LED - use as lock solenoid indicator 183 pinMode(lockSolenoid, OUTPUT); 184 digitalWrite(lockSolenoid, HIGH); 185 // external LED - use as mode indicator 186 pinMode(modeLED, OUTPUT); 187 digitalWrite(modeLED, HIGH); 188 189 lockState = FIRST_SCAN; 190 activity_timer.setEnable(true); 191} // end of setup 192 193// 194//-------------------------------------------------------- 195void loop() { 196 // If no activity cancel any combo setting modes and 197 // return lock to INIT_ENTER state. 198 // If power save option enabled, go to sleep. 199 200 // Is it time to reset or go to sleep? 201 if (activity_timer.getDone()) { 202 isChangeComboFlag = false; 203 lockState = INIT_ENTER; // cancel any leftover active modes 204#ifdef POWER_SAVE 205 goToSleep(); 206#endif 207 } 208 // Refresh the timers 209 210 detentFlash_timer.update(); 211 solenoidON_timer.update(); // onDelay_tmr 212 modeFlasher_timer.update(); 213 NewCombo_timer.update(); 214 activity_timer.update(); // no activity for X seconds initiates 215 // state machine reset or sleep mode 216 // 217 dialMoved(); // Register dial rotation and direction 218 encoderFlasher(); // Flash the onboard LED to verify movement 219 220 //----------------------------------------------------------- 221 switch (lockState) { 222 /* This state machine guides us through the several phases of 223 lock operation. 224 */ 225 case FIRST_SCAN: 226 227 /* Fallthrough is deliberate */ 228 229 case INIT_ENTER: 230 clearTumblers(); 231 lockState = ENTER_COMBO; 232 isChangeComboFlag = false; 233 codeError = false; 234 235 /* Fallthrough is deliberate */ 236 237 case ENTER_COMBO: 238 getEntries(); // Record code inputs 239 if (codeError) { 240 Serial.print("code error\n"); 241 lockState = INIT_ENTER; 242 break; 243 } 244 if (readEncoderPB(PB_PULSE)) { // User request to open lock 245 lockState = INIT_OPEN; 246 } 247 break; 248 249 case INIT_OPEN: 250#ifdef DISPLAY_LOCK_CODE 251 displayTumblers(); // display lock code & submitted code 252#endif 253 if (memcmp(lockCode, tumblerData, MAX_TUMBLERS) == 0) { 254 // Correct combination entered, enable lock solenoid 255 solenoidON_timer.setEnable(true); 256 lockState = OPEN_LOCK; 257 } 258 else { 259 lockState = INIT_ENTER; // wrong code, start over 260 } 261 break; 262 263 case OPEN_LOCK:// Actuate the opening mechanism and begin 264 // testing for set new combo request 265 NewCombo_timer.setEnable(!readEncoderPB(PB_STATE)); // Start waiting for set mode 266 267 // If encoder PB pressed for one second while solenoid is on, set 268 // isChangeComboFlag to enable prompt for new code 269 if (NewCombo_timer.getDone() and solenoidON_timer.getRunning()) { 270 isChangeComboFlag = true; // When button pressed for one second 271 } 272 if (solenoidON_timer.getDone()) { // Solenoid on time ends 273 solenoidON_timer.setEnable(false); 274 NewCombo_timer.setEnable(false); 275 if (isChangeComboFlag) { 276 lockState = INIT_NEW; 277 } 278 else lockState = INIT_ENTER; 279 break; 280 } 281 else break; 282 283 // User request to change lock code 284 case INIT_NEW: 285 clearTumblers(); 286 modeFlasher_timer.setOnTime(500); // Set flash ratio 287 lockState = NEW_COMBO; 288 289 /* Fallthrough is deliberate */ 290 291 case NEW_COMBO: 292 getEntries(); // Record new code inputs 293 if (codeError) { // Too many codes entered, abort 294 Serial.print("code error\n"); 295 lockState = INIT_ENTER; 296 break; 297 } 298 if (readEncoderPB(PB_PULSE)) { 299#ifdef VERIFY_NEW_COMBO 300 lockState = INIT_VERIFY; // If verification option #defined 301 break; 302#endif 303 // Set new combo, no verify 304 setNewCombination(); // Xfr new code to storage 305 lockState = INIT_ENTER; // Return to idle state 306 break; 307 } 308 break; 309 310 case INIT_VERIFY: // Prepare for new code verification 311 // Copy first code to staging array 312 memcpy(newCode, tumblerData, MAX_TUMBLERS); // Make a copy of the new code 313 clearTumblers(); 314 modeFlasher_timer.setOnTime(950); // Change flash ratio 315 lockState = VERIFY_COMBO; 316 317 /* Fallthrough is deliberate */ 318 319 case VERIFY_COMBO: 320 getEntries(); // Collect verification code 321 if (codeError) { // 322 Serial.print("code error\n"); 323 lockState = INIT_ENTER; 324 break; 325 } 326 if (readEncoderPB(PB_PULSE)) { // User requests new code verification 327 if (memcmp(newCode, tumblerData, MAX_TUMBLERS) == 0) { // compare reentered code with previous 328 // 329 // Codes match - store to eeprom and also to lockCode. 330 setNewCombination(); 331 lockState = INIT_ENTER; // Return to idle state 332 break; 333 } 334 // Codes don't match, restart new code verification process 335 else lockState = INIT_NEW; 336 } 337 break; 338 default: 339 lockState = INIT_ENTER; 340 break; 341 } // End of lockState switch 342 343 //------ O U T P U T S --------- 344 345 modeFlasher_timer.setEnable(isChangeComboFlag); 346 if (modeFlasher_timer.getEnable()) { 347 digitalWrite(modeLED, !modeFlasher_timer.getFlash()); 348 } 349 else digitalWrite(modeLED, LOW); // Leave mode LED on to indicate wakefulness 350 351 digitalWrite(lockSolenoid, !solenoidON_timer.getRunning()); // energize lock solenoid 352 353 // digitalWrite(detentFlashLED, detentFlash_timer.getRunning()); // LED on 354 digitalWrite(detentFlashLED, !detentFlash_timer.getRunning()); // LED on 355} 356// -------------- E N D O F L O O P ==============
Downloadable files
code
...
Code Final.zip
Documentation
Schematic
...
Schematic.jpg
Comments
Only logged in users can leave comments
asdfe32
3 months ago
You're not saving any bytes by doing fancy math tricks on an Arduino's architecture. However if you actually used the constants you've declared you would save some bytes.