Water scarcity is a real issue throughout the world, so paying attention to your water usage and being mindful of ways you can save water can help future generations.
Saving water at your own home is easy to do. You install a toilet bladder, avoid watering your flowers and yard in the midday sun, and harvest rainwater and you’ve already made a great dent in your usage.
Saving water when you’re away from home can be a little trickier, though. You’re not going to carry around a toilet bladder to use at a restaurant or friend’s house. These tips will help you feel good about your water-saving efforts, no matter where you are.
Use a Refillable Water Bottle
Conserving water can be as simple as considering which container you’ll drink it out of. To use pick the best container, buy a refillable water bottle or flask that you can carry with you in your car, bike, or while walking.
Disposable water bottles take water to produce. So do refillable bottles, but you’ll be reusing those hundreds or thousands of times compared to a single-use. It’s smarter and better for the environment if you stop using disposable water bottles entirely.
Reuse Towels at a Hotel
Chances are, you reuse your bath towels multiple times before washing them at home. Our to-do lists are long enough without having to wash and fold extra laundry unnecessarily. Who wants to do that?
But at hotels, many of us feel more indulgent. We don’t treat our hotel rooms like we’d treat our house, and we leave those soggy towels on the bathroom floor, secure in the knowledge new ones will be brought to our rooms the next morning.
But with laundry making up as much as 16 percent of the water usage at a hotel, those reused towels can add up quickly in terms of saved water and reduced bills. It just takes a second to hang up your towels, and there’s no reason you can’t use them for the full week of your vacation.
Depending upon the length of your vacation and how many towels are in your room, you could save one load of laundry during your trip just from reusing towels. That can save at least 14 gallons of water or more if the hotel doesn’t use an energy-efficient appliance.
Eat Locally When on Vacation
When you’re travelling, look for roadside fruit stands and restaurants that advertise using locally-grown foods. Transportation of food from one country to another or one state to another can add a lot to the water footprint of the food you eat. It takes resources and energy to get that food moved.
The fruit you buy at stands hasn’t been washed as much as packaged foods in your grocery store has, and it generally isn’t packaged in plastic that takes water to produce. If you don’t see any packaging on your food, you’re saving water usage just from that.
Pick Foods Low on the Food Chain
Foods higher on the food chain take more water to produce. The bigger the animal you’re about to consume, the more water it took to get that food on your plate.
Beef takes about 1,847 gallons for every pound of meat, compared to pork at 718 gallons per pound. Chicken takes less water to produce than either of those meats.
Even better, opt for a few meatless meals every week while eating out in restaurants or on the road.
It takes 1,056 gallons of water to make one gallon of brewed coffee.
A gallon of beer requires 296 gallons of water to produce.
A gallon of wine takes 872 gallons of water to make.
If you’re tired of water, reach for tea. It only takes 108 gallons of water to produce one gallon of tea.
Carpool or Ride Your Bike
Producing gasoline takes water, so the less fuel you burn on driving, the less water you’ll be using. To conserve water, see if you can start an office carpool. It will save you and your coworkers money while also cutting down on water usage.
A better bet if you can do it is riding your bike or walking to wherever you’re going.
It All Adds Up
Sometimes the steps we take to save water can seem so small in the grand scheme of things. But every little bit truly does add up. And if enough people make a few changes to conserve water when they’re away from home, it will make a big difference in the long run.
What are some of the ways you save water outside of your home? Let us know in the comments section below.
Once mould starts growing on caulk or silicone joints and corners in your bathroom, it can be quite difficult to clean. You can scrub away for hours using bathroom cleaners and not really make much progress. We’ve found a solution that works well to remove mould on caulk and doesn’t require any scrubbing, you probably even have most of what you need in your home already.
You’ll need to mix the baking soda together with the bleach in your container. Start by adding a small amount of each and work until you run out of one of the ingredients. The mixture should form a paste which is quite thick and not drip off of the toothbrush, similar to toothpaste. The quantities are approximate and may need to be adjusted a little depending on how thick your bleach is.
Once you’ve got your thick bleach and baking soda paste, head into your bathroom and open up a window or turn on the extractor fan to get some fresh air flowing through the room. The mixture has a strong smell when you start spreading it out. You might also want to wear rubber gloves so that you don’t get it on your hands, undiluted bleach can be quite harsh on your skin and may cause burns or an allergic reaction.
Use the toothbrush to gently brush the mixture onto the mouldy spots, completely covering them. Make sure that the paste is thick enough to be held in place for an hour or so, it shouldn’t run down or drip off of the joint but should also completely cover the mould and shouldn’t be crumbly.
Note: You might want to test the mixture in a small hidden area to make sure that it doesn’t cause discolouration of your tiles.
Once you’ve brushed the mixture onto all of the mouldy spots, you just need to wait for the bleach to do the work. This usually takes about an hour, depending on how bad the mould is. It’s a good idea to check on the paste halfway through to make sure that it’s still in place.
After an hour, use some warm water and the toothbrush to wash the paste away. Make sure that it doesn’t drip onto your floor or bathmat or towels in the process as it may discolour them.
Once you’re done, the mould should be gone, or at least significantly lighter. Sometimes stubborn mould requires a second treatment.
Have you tried this method to clean the mould from your caulk or silicone joints and corners? Let us know what worked for you in the comments section below.
In this project, I’m going to be showing you how to build your own Arduino based crack the code game. You use a dial to guess the randomly generated code to the safe and 8 LEDs on the front of the safe then tell you how many of the digits you’ve guessed are correct and how many are in the right place as well. Using this feedback you can work your way towards cracking the code to the safe, and once you do, the game will then tell you how many attempts it took you to crack.
Here’s a video of the build. Read on for the full step by step instructions.
This project assumes you know the basics of Arduino programming, otherwise read our article on getting started with Arduino.
There have been a number of different forms of this game over the years, from physical pegboards in the 70s to video games, and more recently you may have seen these types of puzzles as single images on social media platforms.
The safe is initially open, allowing you to put something into the inside compartment. You then push the dial to lock the safe using a servo on the inside of the door and a random code is generated.
You then need to input the code by turning the dial to select the digits and pushing the dial to confirm each digit.
After your fourth digit is chosen, the safe displays how many of your digits are correct and how many of them are in the correct place using the red and green LEDs on the door. A red LED indicates a correct digit and a green LED indicate that it’s also in the correct place. So you’re looking to light up all four red and green LEDs in order to crack the code and open the safe.
The safe keeps track of how many guesses you’ve made in order to crack the code. It may sound complicated at first but it’s actually not that difficult. You just need to remember and build upon your previous guesses. Most of the time you should be able to crack the code in 5 to 10 guesses, depending on how lucky your initial guesses are.
What You Need To Build Your Own Crack The Code Puzzle Box
In addition to these, you’ll also need some basic tools, a soldering iron, glue gun and some wood glue. You’ll also need to have access to a laser cutter to cut the box. If you don’t have access to one, consider using an online laser cutting service. There are quite a few options available and they’re quite affordable, they’ll even deliver to your door.
If you’re interested in a laser cutter, this is the one I’ve used for this project.
I started off by designing the safe box in Inkscape. It had to be large enough to house an Arduino Uno, have a compartment to put something into and look like a traditional safe. The pieces are designed to be cut from 3mm MDF, you could also cut the parts from 3mm acrylic or plywood. If you use a different thickness material then you’ll need to modify the edges of the box pieces so that the slots fit together.
The download above includes svg, dxf and A3 printable pdf files to give you a couple of options for cutting or printing.
There are 6 main panels which make up the box. The front panel has a cutout in it for the door and the back has one for the Arduino and battery compartment cover. The 6 main panels are labelled in the cutting file so that you’re able to identify them.
The dial is also made up from laser-cut pieces which are then glued together.
There are three decorative panels which are stuck onto the top and two sides of the box to make it look more like a safe. There are also two panels which make up the door and a divider panel which goes into the middle of the box to separate the safe compartment from the electronics compartment.
The pieces fit onto a single piece of MDF 400 x 500mm. They can be divided up into smaller pieces, as I’ve done, if your laser cutter isn’t big enough to cut them all in one go. I cut out two sections at a time.
Let’s cut out the pieces.
I used some masking tape to prevent the smoke from the laser from marking the wood. I had to take this off before gluing the box together, which is a little tedious but produces a much better end product.
I started out gluing the decorative panels onto the top and sides first using some PVA wood glue and small plastic clamps. Make sure that you’ve got the pieces in the correct order so that you know which are which. There are three different pieces; the top and bottom are the same, the sides are the same and the front and back are the same. I’ve put text labels under each in the laser cutting file. Then glue the two door panels together.
Once the panels are dry, you can assemble the box.
Make sure the cutouts for the centre divider are on the sides. These are to run any wires from the front of the box to the back of the box where the Arduino and battery sit.
The hinges are also laser cut and just glue into place once you’ve lined up the door. Make sure that they’re parallel to the door or you’ll have difficulty opening it. Also, make sure that the door is positioned over the centre of the cutout in the box so that there are no gaps visible.
You may also need to sand a little bit off of the inside hinged edge of the door once the hinges are dry so that it doesn’t rub on the inside edge of the box cutout as it moves past.
Glue the four square pieces into the corners behind the back panel to hold the screws for the back cover in place.
Then drill holes for the screws. I used a couple of spare screws I had lying around which came with the micro servos.
You can then mount the screen onto the door with two screws, the Arduino into the back compartment and lastly the encoder through the front door. The nut for the encoder is hidden by the recess provided by the front panel.
Connecting The Electronics
Now that the box is complete, we can start connecting the electronic components together.
Here is the schematic:
We’ve got 8 LEDs connected to the digital IO pins 6 to 13. The locking servo connected to pin 5. The encoder connected to pins 2, 3 and 4 and the OLED display connected to the Arduino’s I2C interface.
I used a 220 ohm resistor for each LED, which I soldered directly onto the cathode (negative pin) of the LED before gluing them into place with a glue gun.
I connected the components together using coloured ribbon cable to keep the wiring neat and to help keep track of which wire needed to go to each Arduino pin.
I pushed the ribbon cables through to the back compartment and the soldered sections of header strips onto the ends to plug directly into the Arduino.
I also mounted a power switch onto the back cover and connected this to a battery plug to connect to a rechargeable battery to power the game. I used a 3 cell, 800mAh LiPo battery which I had from some RC aircraft. You could also use a 9V battery or a battery holder which houses 4 AA batteries.
Lastly, you’ll need to mount the locking servo on the door. Position it towards the edge of the door so that it passes over the lip in the box and the arm is able to push up against the inside of the lip to lock the box. This isn’t the strongest locking mechanism but it is really simple and it works for the game’s purpose.
The safe box is now complete.
Coding The Game
The code for this game isn’t that complicated but is quite lengthy. I’ve tried to make the code as readable as possible by splitting it up into functions and putting in a lot of comments.
The code makes use of an interrupt routine to take the inputs from the rotary encoder, this is based on Simon Merrett’s example for a rotary encoder input which I found online. It was the most simple I could find and didn’t require any additional libraries to function.
Here is the code:
//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 white
startupAni(); //Display the startup animation
}
void loop()
{
if(correctGuess) //Code between games to reset if the guess is correct, initially true to open safe and then generate new code
{
lockServo.write(140); //Unlock the safe
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
{
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("Lock Safe")); //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(35,10); //Set the display cursor position
display.println(F("Crack")); //Set the display text
display.display(); //Output the display text
delay(500);
display.clearDisplay(); //Clear the display
display.setCursor(45,10);
display.println(F("The"));
display.display();
delay(500);
display.clearDisplay();
display.setCursor(40,10);
display.println(F("Code"));
display.display();
delay(500);
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
}
We start by importing libraries to control the OLED display and the servo. The libraries are used to drive the OLED display, you can find more information on driving these OLED displays on Adafruit’s page on using I2C OLED displays.
We then set the parameters for the display and create all of our variables.
I’ve separated the variables into sections and given each a comment to identify their function. There are quite a few variables dedicated to tracking the encoder turns as these are done through rising edge interrupts on pins 2 and 3. The LED pins are assigned through to arrays which make updating them a bit easier. There are two code arrays created, on to store the randomly generated code and one to store the user’s current guess. There are also a few variables to track whether the uses code is correct, which digit is currently being guessed and how many guesses the user has made so far.
In the setup function, we start the display, which won’t progress unless the connection to the display is successful. Then attach the servo, set the LED and encoder IO pin modes. We then make use of the randomSeed function which reads in from an open analog input in order to randomise the starting point for the random function. If we don’t do this then the randomly generated codes will follow a predictable pattern which we’ll soon be able to identify. Lastly, we display the Crack The Code text animation on the display.
The loop function flashes the LEDs and displays the message “push to lock safe” which then waits until the user pushes the dial to start the game. The same code is run at the end of a game which then displays the number of attempts and waits for a dial press to start a new game.
There is some debouncing code on the encoder pushbutton and once pushed, the servo locks the safe and a random code is generated. The code then calls a function to ask the user to input their guess and then another to check the guess, this is repeated until the user guesses the code correctly. After each code guess, we increment the number of guesses counter and reset the input code so that the user can select a code from 0000 again. You could also change this to carry on from the previous code again if you’d prefer.
There is a function to update the code being displayed, which is called every time the encoder is turned and the displayed code needs to change. This just loops through the code guess array and displays the four digits.
The function to generate a new code simply assigns a random digit to each of the four elements in the code array.
The function to input a code guess allows the user to select a digit using the encoder and then confirm each digit input by pushing the encoder down. The displayed code is updated after each button push and any time that the encoder position has been changed. So the encoder updates the position variable using the interrupt function and this portion of the code checks if the variable has been changed and then updates the code accordingly. The interrupt doesn’t directly drive any portions of the code, it only updates the encoder position variable.
The check code guess function then looks through the guessed code and decides how many digits are correct and how many are in the correct place. There is more code to the logic behind the digits in the incorrect place so that duplicates in the user’s input and in the generated code are managed. So if the user guesses 5555 and the code contains one 5 then only one of the red LEDs will light up. The same goes for the user guessing a single 5 in their code while the generated code contains two 5s, only one red LED will light up. The correct digit in the correct place check is a simple comparison of the digits in each position of the two arrays.
The update LEDs function switches the correct number of red and green LEDs on based on the output from the check code guess function.
The startup ani function displays the three-word “Crack The Code” animation on startup.
Lastly, two interrupt functions manage the input from the encoder, one incrementing the digit upwards when turned clockwise and one downwards when turned anticlockwise. These are both rising edge interrupts available on pins 2 and 3 of an Arduino Uno. These could also be set as falling edge or low level interrupts. You can read more on the Arduino’s available interrupts if they interest you.
Now let’s upload the code and try it out.
Edit: Modify Code To Start With Locked Box
I’ve had quite a few people ask about how to start the box off locked rather than with the current push to start the game and lock the box. This is quite easy to change, you just need to modify three lines:
Add lockServo.write(45) into the setup function (at the end) to lock the safe when the box is turned on.
Move line 73 – lockServo.write(140) into the if statement below it (at the beginning). The statement starting on line 84, so insert it into line 86. This will then only unlock the box again once the guess is correct.
Change the displayed text in line 101 to read display.print(F(“Start”)). As the box will already be locked on startup.
This will then start the box off locked. You’ll still need to press the encoder to start the game but this won’t do anything to the lock in the first round as it will already be locked. In subsequent games, starting the game will lock the box again.
Playing The Game and Trying To Crack The Code
The guessed code is input using the dial to increment the digit and a push on the dial to go to the next digit or to confirm the code once all four digits are selected.
The LEDs on the front then light up to tell us what was correct in our guess. The red LEDs indicate that you’ve got a correct digit but not necessarily in the correct place and the green LEDs indicate that the digit is also in the correct place. The position or order of the LEDs is meaningless, they don’t give you any further information. Remember that you’ll need to be careful with double digits as the LEDs don’t light up multiples times for repeated digits unless you’ve repeated the digit in your guessed code.
The safe is initially unlocked, allowing you to put something inside it.
We then push the dial to lock the safe and generate a new code. You then have to try and figure out the code in order to open the safe again.
Once you’ve cracked the code, the box will unlock and display the number of attempts it took you to unlock it.
The easiest way to see how the game is played is to watch the video at the beginning which has two demonstrations near the end.
Enjoy building your own crack the code safe box. Let me know how it goes for you or what changes you’ve made to it in the comments section.
Community Builds
Asaf Matan has made some fantastic modifications to the puzzle box to make the storage area a little larger, separate the electronics and to run on an ESP32 instead of an Arduino. Take a look at his blog post for the code and some additional build images:
The term “cylinder lock” could mean a large number of different locks, but in today’s guide, we will focus on one type of cylinder – the Euro cylinder. The Euro cylinder was first patented in 1805 and was an attempt by European locksmiths to standardise locks, which, in Europe and other parts of the world has been largely successful. Euro cylinders are now a common sight on the external doors of many homes. In the United States, Euro cylinders are used primarily on sliding glass doors and internal doors which divide two rooms.
Euro cylinders are easy to change or upgrade, and here at The DIY Life, we love to save you both time and money. There is no need to call a locksmith or handyman as these are one of the easiest locks to change, due to the cylinder being self-contained, their standardisation and wide availability. In fact, the most important part is the preparation.
To Change A Euro Cylinder You Will Need
1 Phillips Screwdriver OR 1 Torx Screwdriver (depending on the screw type)
1 Euro Cylinder Lock (Do NOT BUY before you have extracted the lock as you need the correct size)
Your existing Euro cylinder key
How To Change The Lock
1. Firstly open the door so you can comfortably stand by the side of it.
2. Next, locate the Euro cylinder on the door, then locate the central screw on the side of the door. This is the screw that keeps the Euro cylinder in place.
3. Identify which screwdriver is appropriate to use, then remove the screw. Normally this will be a Phillips, but may also be a Torx screw and very rarely may be a flat head.
4. Once the screw is removed, insert your key into the lock and turn it about an 8th of the way or to approximately to 2 O’clock, this is to align the cam (middle of the lock). Once aligned, pull the key and lock towards you until you feel a release. You sometimes need to loosen the screws to the door handle to make this easier, but will not usually be necessary. This can be a bit tricky, but just remember, all it is is an alignment issue. Loosen the door handle, turn the key to align the cam and wiggle the lock out.
5. Now the Euro cylinder should be in your hand, giving you the opportunity to measure it. You can measure the size of a Euro cylinder by finding the central screw hole on the lock. Grab a tape measure and measure from the central screw hole to the end of the cylinder, measure both ways. Your measurement should be something like 35mm by 35 mm (As seen below) or 45mm by 50mm etc. However, this size is only correct if your lock is the correct size for the door in the first place. The cylinder should be flush to the door with none of the cylinder being exposed. If the cylinder is exposed then measure from the central screw hole to where the lock would fit flush to the door, this should be identifiable with a weathered line on the lock, where some of the lock has been exposed to the elements. This exposure of the lock is a serious security risk and makes bypassing the lock much easier with a technique called lock snapping (more on that later).
6. There are approximately 25 different sizes, so ensure you have measured the size correctly. Alternatively, if possible, you could take the lock with you to ensure you purchase the exact size needed.
7. Now travel to your local hardware store and purchase the correct size replacement cylinder.
8. Once you have the new cylinder, insert your new key into it and turn it an 8th of the way, so the cam (the middle) is aligned, allowing you to insert the lock into your door. The exact same way you took it out but in reverse. This bit can be as tricky as getting the lock out, remember it’s just an alignment issue so just fiddle around and it will eventually come right.
9. Once the lock is fitted nicely and is flush with the door, use the old screw you took out of your previous lock and insert it into the central screw hole and tighten it. We recommend using the old screw as it will be the correct length, sometimes a new screw will be too long and will have to be cut to size. Alternatively, if the new screw is the same size and doesn’t require cutting, then use the new screw. Don’t forget to keep your cylinder measurement for future maintenance.
10. To test that it has all been done correctly, lift the handle and turn the key to lock the lock with the door open. Lock the cylinder a few times to test it. Once you have seen this working it properly, it should be safe to shut the door and lock it.
Euro cylinders are great locks and are renowned for being difficult to pick, however, they do possess some natural weaknesses. one big vulnerability to the security of your door is called snapping. Lock snapping is exactly what the name suggests, it literally means snapping the lock in two. The scariest thing is that this technique can be successfully done in as little as 15 seconds, and the tools required to snap a lock can be found almost anywhere. This can be overcome by purchasing anti-snap locks with a sacrificial section. Instead of the lock snapping in half, the sacrificial section will break off leaving the rest of the lock within the door, safe from another snapping attempt.
Another vulnerability of the euro cylinder is drilling. Like most locks, the Euro cylinder can be drilled within minutes. While this cannot be overcome, you can purchase euro cylinders with anti-drill pins within them. This does not stop drilling, but it does make drilling a harder and longer process.
Generally higher quality Euro cylinders are made out of stronger and sturdier materials, they also contain more anti-drill pins, in addition to other security feature,s such as anti-bump pins, sacrificial sections and magnetic operating locks, making picking extremely difficult. If your Euro cylinder is for an external door you should consider purchasing a high-security lock, as this will be much more resistant to any bypassing techniques used by criminals, making it harder for them to enter your property. More on this and other helpful security tips and advice can be found on ITCC Locksmith’s blog section.
Have you tried changing your own Euro cylinder lock? Let us know in the comments section below.
Few home decor accessories look as regal as a chandelier. The problem is that chandeliers usually cost a pretty penny. Whether you’re on a budget or just looking for a new home improvement DIY project, making your own chandelier is fun, easy, and surprisingly affordable.
As you read this guide on chandelier making, remember that these are just suggestions and not requirements. You can use the basic design as-is, or feel free to get creative and modify it to fit your unique style preferences. The possibilities are endless!
1. Gather Materials
If you do a lot of DIY projects, there’s a good chance you’ll already have a lot of these materials lying around the house. If not, you should be able to obtain them fairly easily—check hardware, junk stores, and other retailers that sell lamp and light fixture parts.
If you’re struggling to find a particular item, try to think outside the box. You should be able to come up with a solution, even if it’s not exactly what you initially had in mind.
Here’s a general idea of what types of materials you’re going to need:
A top base to hold the suspended items (repurposed wood, a bicycle wheel, or a wire cooking rack are all great choices)
At least 10 feet of lightweight wire lamp chain or other lamp cord
14-gauge (1.5mm^2) wire
At least six small Mason jars (or exposed light bulbs of your choice)
A one-inch circular metal ring
A swag hook or other hardware for hanging the chandelier
Pliers
Wire cutters
2. Attach the Chains or Cord to Your Top Base
The metal ring is going to be your point of contact to the ceiling. When you’re finished with your chandelier, you can use the ring to hang it from a hook. Start by using your wire cutters to cut the lamp chain or cord into four equal-sized pieces. The length depends on the size of your base, but around 16 (400mm) to 24 (600mm) inches should do the trick.
If you’re using chains, take your pliers and open the top link of each chain. Attach the chain to the metal ring, and then use your pliers to squeeze the chainlink back into place. Repeat this step for all four chains. You should end up with four equal lengths of chain hanging from the ring.
If you’re using a flexible cloth-covered cord, consider wrapping the material around your base in a way that matches the aesthetic you’re going for.
3. Attach the Cord to the Base
Now you’re going to repeat the same process to attach the bottom of the chains to your wheel, wire cooking rack, or other base. If your base is thicker than your chain links, you might need to get creative. You could use wire to wrap the bottom chain link to the perimeters of your base.
When you’re done attaching all four chains, you should be able to hold the top ring, and the base should hang evenly. If it’s crooked, you might need to trim the chains to make sure they’re all the exact same size.
4. Create Wire Handles on the Jars
Credit: Color Cord Company
Next, you’re going to create wire ‘handles’ so you can hang each jar from the base. Cut your wires so they’re 24 inches (600mm) long. Wrap the wire securely around the lip of your Mason jars, leaving at least 8 inches (200mm) of slack like a tail. You’ll attach the other end of the wire ‘tail’ to the jar eventually, but not yet.
Pro tip: You could also leave the lids on, drill a hole in the tops of the lids, and attach the jars directly to the base using rope or twine for a rustic look. Simply thread it through and tie a knot in the bottom to prevent it from slipping out.
5. Thread the Wires Through the Bottom Chain Links
Cut six (or more) pieces of lamp chain to hang the jars. You can cut the chains into random lengths, or measure them if you want a symmetrical design. Now it’s time to thread the ‘tail’ of the wire handles on your jar through the bottom chain links. Secure the end of the wire to the other side of the jar, creating a U-shaped handle. Repeat these for all of the jars.
6. Attach the Jars to the Base
Now you have six or more jars with chains attached. You might need a friend for this next step. Hang your base from the ceiling, and attach the top of the chains to the base, one-by-one. Be sure to position them in areas that keep the weight of the structure balanced. It’s important to attach the jars while the chandelier is hanging, otherwise, you risk it being lopsided.
7. Add More Flair
You can leave the chandelier as-is if you want a classic, bucolic look. Minimalism is a popular architecture trend, so feel free to keep it simple. For a stronger visual effect, you could use your leftover light cord or chain to hang beads, crystals, shells, ornaments, antique bottles, or anything else that catches your eye. Feel free to get creative with it.
If you really want to make your new chandelier pop, consider painting it. A quick coat or two of spray paint will help to transform an old wheel or baking rack into a more realistic base. A coat of white paint is a safe and elegant option, but don’t be afraid to match it to your home’s unique colour scheme.
8. Light It Up
Congratulations, your chandelier is finished! The last step is to light it up and enjoy. If you’re using candles, simply light them and drop them into the jars. Just make sure you follow candle safety guidelines to prevent fires.
If you prefer electricity, you could also consider using lamp parts and an extension cord or even wireless LEDs to add bulbs to the jars. Or, you could consider hanging your chandelier underneath an existing ceiling light fixture so the light can radiate off the glass naturally. Be sure to follow electrical safety guidelines if you’re doing any wiring on your own, or hire a contractor to help you with wiring your new light fixture.
Note: Make sure that you check your local regulations before doing any electrical work, some area do not permit unlicensed electrical work to be carried out.
Keep Adding To Your Chandelier
The great thing about this project is that you can keep modifying it over time. Just keep some spare chain and wire on hand, and you’ll be able to attach new pieces whenever the mood strikes. If your first chandelier doesn’t turn out as well as you were hoping, learn from your mistakes and try again! The materials are cheap, so why not experiment with your design?
Have you tried making your own chandelier? Let us know what you made in the comments section below.
You may have seen people using an Arduino to automate playing the Chrome Dino Game in their browser, I thought we could take it a bit further and see if we could get one Arduino to play the Chrome Dino Game on another Arduino.
I’ve previously set up an Arduino to run a simple version of the game, which produces a continuous stream of randomly spaced cactuses at an increasing speed, and closer together. The game is really easy to get running and only requires an Arduino Uno and an LCD keypad shield. A single button on the keypad shield is then used to make the dinosaur jump over each cactus until the player messes up and hits one. The score is kept based on how long you’re able to avoid running into a cactus.
Once I had the game running on one Arduino, I set up a second one, which uses an LDR to sense each cactus and then moves a servo to press the button on the other Arduino to make the dinosaur jump over each cactus.
Here’s a video of the build and the one Arduino playing the Chrome Dino Game on the other:
We’ll first have a look at running the dino game on an Arduino and then set up a second to play the game on the first. The code would be the same to set the second Arduino up to play on your computer’s browser as well, you’d just need to stick the LDR to the display.
Let me start out by saying – I didn’t code the dino game. It’s a version I found on Hackster.io which was the least complicated layout and ran the smoothest. One downside is that it’s written in AVR C code, so it looks a little different to the generic Arduino language but you should still be able to figure a lot of it out, I’ve also expanded on the description of the game and explained how you can change a few things around in another post – How to Play the Chrome Dino Game onto an Arduino.
Here’s a general overview of the game:
You push a button on the keypad shield to make the dinosaur jump over each cactus.
The game speed up the longer you play it.
Cactuses are initially separated by a minimum of 5 spaces and this goes down to 4 as the game progresses.
The current score is displayed throughout the game and is joined by the high score at the end of the game.
Cheating by holding down the button or continuously pressing the button is prevented.
I have made a couple of changes to the original version of the code for the timing, cactus spacing and the text layouts to better accommodate the sensor.
Here is the code:
//From Hackster.io
//By BRZI
#include <avr/io.h>
#define F_CPU 16000000UL //Our CPU speed (16MHz)
#include <util/delay.h> //Libraries for delay and interrupt utilities
#include <avr/interrupt.h>
#define command 0 //explained in dispSend() function
#define write 1
uint8_t upperBuff[16] , downerBuff[16], overMsgUpper[] = "Score: ", overMsgDowner[] = "Best: ", scoremsg[] = "Score:" , din[] = {0x0E, 0x17, 0x1E, 0x1F, 0x18, 0x1F, 0x1A, 0x12}, cact[] = {0x04, 0x05, 0x15, 0x15, 0x16, 0x0C, 0x04, 0x04};
//Buffers for line one and two. Message to display after lost game. //Score text during game. //Dinosaur and cactus bitmaps
uint8_t canup = 1, longhold = 0, distance = 6, speed = 200, isup = 0, dontprint = 0; //All of these are explained further
uint16_t aVal = 0, score = 1, bestscore = 0;
int i;
void dispInit();
void dispWrite(uint8_t bits);
void dispSend(uint8_t bits, uint8_t act);
void dispSetLine(uint8_t line);
void dispClear();
void dispHome();
void dispPrintChar(uint8_t chr[], uint8_t size);
uint16_t aRead();
int main(void)
{
for(i = 0; i < 17; i++) downerBuff[i] = ' '; //Initialize upper and downward buffer
for(i = 0; i < 17; i++) upperBuff[i] = ' ';
dispInit(); //Initialize the display
TCCR1B |= (1 << WGM12) | (1 << CS11); //Set Timer1 to compare to OCR1A and prescaler of 8
OCR1AH = (500 >> 8); //This equals to 2000Hz or 500us timing, look for TIMER1_COMPA_vect down below
OCR1AL = 500;
TIMSK1 |= (1 << OCIE1A); //Enable Timer1 CompA ISR
sei(); //Enable global interrupt
ADMUX = (1 << REFS0); //Set AREF to VCC
ADCSRA = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADEN); //set ADC prescaler to 128 and enable ADC (defaulted to free running mode)
while (1) {
ADMUX |= (1 << MUX2) | (1 << MUX0); //Set pin from ADMUX to ADC5 (floating)
srand(aRead()); //Use it as a random seed
ADMUX &= ~(1 << MUX2) & ~(1 << MUX0); //Revert back to ADC0 to read the button value
if(aRead() > 900) longhold = 0; //Reads if Up button has been released to prevent cheating. The value is so low because if you hold your fingers beneath one of the buttons the voltage would drop, this prevents the dinosaur from locking up
for(i = 0; i < 16; i++) downerBuff[i] = downerBuff[i + 1]; //Shifts everything in downward buffer by one place to the left
if((rand() % 100) > (rand() % 100) && !dontprint){ //This portion decides if it should put a cactus or a blank spot, dontprint is used to prevent cactus grouping
downerBuff[15] = 0x01; //0x01 represents the cactus (we added cactus and dinosaur to CGRAM when we initialized the display)
dontprint = 1; //This part acts both as a boolean and a counter to ensure cactus separation
}
else downerBuff[15] = ' ';
char lastchar = downerBuff[3]; //We remember the whats initially added to the downward buffer before replacing it with the dinosaur
if(!isup){ //If din should be placed down
downerBuff[3] = 0x00; //Place it down
dispSetLine(2);
dispPrintChar(downerBuff, sizeof(downerBuff)); //Draw it
downerBuff[3] = lastchar; //Place back previous thing to the buffer
canup = 1; //This flag is used to disable dinosaur from getting up before it was drawn down, in this case he can go up
} else { //If din should be placed up
upperBuff[3] = 0x00; //Place it up in upper buff
dispSetLine(1);
dispPrintChar(upperBuff, sizeof(upperBuff));
dispSetLine(2);
dispPrintChar(downerBuff, sizeof(downerBuff)); //Draw it
canup = 0; //In this case he wont go up until rendered on line 2
}
if(dontprint) dontprint++;
if(dontprint > distance) dontprint = 0; //This is the part that ensures cactus separation, it will keep the cactus 3-5 spaces apart minimally (depends on the game progress)
if(isup) isup++; //This part makes sure din is on upper side for 3 loops after he was initially drawn there
if(isup > 4){
upperBuff[3] = ' ';
dispSetLine(1);
dispPrintChar(upperBuff, sizeof(upperBuff));
isup = 0;
}
for(i = 0; i < sizeof(scoremsg); i++) upperBuff[i + 5] = scoremsg[i]; //This part prints the current score during the game
uint8_t cnt = 11;
for(i = 10000; i > 0; i /= 10){
upperBuff[cnt] = ((score / i) % 10) + '0';
cnt++;
dispSetLine(1);
dispPrintChar(upperBuff, sizeof(upperBuff));
}
score++; //Increment the score once on loop
if(score > bestscore) bestscore = score; //Remember best score
if(lastchar == 0x01 && !isup){ //Check if the dinosaur is downward and hit a cactus
dispClear(); //Clear the display and buffers
for(i = 0; i < 17; i++) downerBuff[i] = ' ';
for(i = 0; i < 17; i++) upperBuff[i] = ' ';
uint8_t cnt;
dispSetLine(1);
for(i = 0; i < sizeof(overMsgUpper); i++) upperBuff[i] = overMsgUpper[i]; //Display worst and best score
cnt = sizeof(overMsgUpper) - 1;
for(i = 10000; i > 0; i /= 10){
upperBuff[cnt] = ((score / i) % 10) + '0';
cnt++;
}
dispPrintChar(upperBuff, sizeof(upperBuff));
dispSetLine(2);
for(i = 0; i < sizeof(overMsgDowner); i++) downerBuff[i] = overMsgDowner[i];
cnt = sizeof(overMsgDowner) - 1;
for(i = 10000; i > 0; i /= 10){
downerBuff[cnt] = ((bestscore / i) % 10) + '0';
cnt++;
}
dispPrintChar(downerBuff, sizeof(downerBuff));
while(1){ //Wait for select button to be pressed
aVal = aRead();
if(aVal > 635 && aVal < 645){ //After that clear all the variables
for(i = 0; i < 17; i++) downerBuff[i] = ' ';
dispSetLine(1);
dispPrintChar(downerBuff, sizeof(downerBuff));
for(i = 0; i < 17; i++) upperBuff[i] = ' ';
dispSetLine(2);
dispPrintChar(upperBuff, sizeof(upperBuff));
dontprint = 0;
isup = 0;
score = 1;
speed = 200;
longhold = 0;
distance = 6;
canup = 1;
break;
}
}
}
if(score % 5 == 0) speed -=1; //If score is divisible by 5 make game faster by -2ms
if(speed < 120) speed = 120; //Minimal time in ms (+ ~2ms) that the loop will be halted for (limited by display refreshing, in my testing 11.8Hz was readable enough to be playable)
if(score % 175 == 0) distance--; //Every time you score a number divisible by 175 minimal cactus distance gets smaller
if(distance < 4) distance = 4;
for(i = 0; i < speed; i++) _delay_ms(1); //This is the only way as the compiler expects a const number here
}
}
void dispInit(){
_delay_ms(50); //Just in case
DDRD = 0b11110000; //Set these pins to output. PD4 - PD7 correspond to D4 - D7 on display, we need to configure it to run in 4 bit mode
DDRB = 0b00000011; //PB0 is tied to RS and PB1 to EN
dispWrite(0x30);//*This part here is explained in Hitachi HD44780 datasheet on how to initialize the display in 4bit mode
_delay_us(4500);//*Essentially you send the reset signal 3 times, and then set it to 4 bit mode
dispWrite(0x30);//*
_delay_us(4500);//*
dispWrite(0x30);//*
_delay_us(4500);//*
dispWrite(0x28);//*
dispSend(0x28, command); //Send 4bit mode function set
dispSend(0x08, command); //Turn the display off
dispSend(0x01, command); //Clear its RAM (if MCU resets that doesn't mean the display was reset, so we clear everything)
_delay_ms(50);
dispSend(0x0C, command); //Turn the display on
_delay_ms(5);
dispSend(0x40, command); //Tell the display we want to enter a custom character to its CGRAM (on address 0x00)
for(i=0; i<8; i++) dispSend(din[i], write);
dispSend(0x80, command); //Transaction end
dispSend(0x48, command); //Same thing, but for 0x01
for(i=0; i<8; i++) dispSend(cact[i], write);
dispSend(0x80, command);
}
void dispPrintChar(uint8_t chr[], uint8_t size){
for(uint8_t i = 0; i < size; i++) dispSend(chr[i], write); //Self explanatory
}
void dispSetLine(uint8_t line){
if(line == 2) dispSend(0xC0, command); //Sets the line where 0xC0 is line 2 and 0x80 is line 1
else dispSend(0x80, command);
}
void dispClear(){
dispSend(0x01, command); //Self explanatory
_delay_ms(2); //This command takes longer for the IC to process, this delay is necessary
}
void dispHome(){ //This function isn't used in this application but its there for expandability, it places the cursor on the line 1 column 1
dispSend(0x02, command); //Self explanatory
_delay_ms(2);
}
void dispSend(uint8_t bits, uint8_t act){
if(act) PORTB |= (1 << DDB0); //Set PB0 if we are writing a character, else pull it low
else PORTB &= ~(1<<DDB0);
dispWrite(bits); //Send the bit then shift them 4 bit to the left to work in displays 4bit mode
dispWrite(bits << 4);
_delay_us(80);
}
void dispWrite(uint8_t bits){
PORTD = bits; //This is a dirty way to write it but it's perfect for this application as it's not bulky and PORTD isn't used for anything else anyway
PORTB |= (1<<DDB1); //Pulse the PB1 to signal the IC to read the data
_delay_us(1);
PORTB &= ~(1<<DDB1);
_delay_us(1);
}
uint16_t aRead(){
ADCSRA |= (1 << ADSC); //This signal the avr to read the ADC value
while (ADCSRA & (1 << ADSC)); //Wait until it's finished
return ADCL | (ADCH << 8); //Send it back stitched together
}
ISR (TIMER1_COMPA_vect){ //Timer ISR we set up earlier
if(!longhold){ //Return if the Up button was still held
aVal = aRead(); //Read from ADC0
if(aVal > 450 && aVal < 600 && canup){ //Check if Up is pressed and that din was rendered down
isup = 1;
longhold++;
}
}
}
The game is quite easy to play on the LCD keypad shield, although the buttons are not the best for quick presses, and the LCD is quite slow, so it starts suffering from ghosting and brightness issues once the cactuses start moving quickly.
Setting Up An Arduino To Play The Chrome Dino Game
Now that we’ve got the game running on our first Arduino, let’s try and get the second one playing the Chrome Dino Game on the first.
The components and wiring are quite simple, they involve basic starter circuits for the LDR, LED and servo. I initially included the LED to light up when each cactus was detected but the light started interfering with the LDR, so I turned it off. You can leave this LED out if you want to or cover it up slightly with tape so that it doesn’t interfere with the surrounding components.
I connected the components together using a few strips of ribbon cable and some header pins to make it easier to plug into the Arduino and the servo.
You’ll need to glue the servo onto a box or stand just above the left button on the keypad shield so that the servo arm pushes the button down when it rotates. You’ll then need to glue the LDR onto the edge of the LCD screen as close to the screen as possible. It needs to be positioned over the 7th character from the left of the display in the second row.
Make sure that the LED is covered, turned off or pointed away from the display so that the light from the LED doesn’t interfere with the LDR sensing the cactuses. The first few runs I had were interrupted by the LED causing false cactus readings by the LDR, so it turned it off.
Now let’s have a look at the player code.
//Michael Klements
//The DIY Life
//8 May 2020
#include <Servo.h>
Servo player; //Create a servo object to control the button servo
int cactus = 0; //Variable to read in LDR value
int cactusVal = 770; //LDR Sensor value when detecting a cactus
void setup()
{
//Serial.begin(9600);
player.attach(6); //Set the player servo pin to pin 6
player.write(75); //Set the servo to an initial position just above the button
}
void loop()
{
cactus = analogRead(A0); //Read the output from the LDR to detect a cactus
//Serial.println(cactus); //Used for calibration
if (cactus > cactusVal) //If a cactus is detected
{
player.write(60); //Move the servo to push the jump button
delay(300); //Wait 300 milliseconds
player.write(75); //Move the servo back to the initial position
delay(100);
}
}
We start by including the servo library to control the servo.
We then create a servo object called player to control the servo and then create two variables, one to store the value read from the LDR and a second to store the light level set point when a cactus passes in front of the LDR.
In the setup function we set the servo pin number and set the servo position slightly above the jump button. You’ll also need the Serial monitor for calibration, this is detailed further on.
In the loop function, we read in the LDR sensor level, then compare it to the cactus set point. If the measured level is greater than the cactus set point, indicating a cactus is passing the sensor, then we move the servo downwards to push the button, wait 300 milliseconds for the servo to move and for the game to register the input and then move the servo back up for the next push. The second delay is to avoid repeated servo movements, this could be reduced to 50ms.
I’ve also included a serial monitor printout of the sensor value which is used initially to set the cactus value set point. You’ll need to run the game and display the sensor values and then see what value is measured when a cactus runs past the sensor and update this value in the code accordingly. This step is detailed in the video at the beginning of the project.
One Arduino Playing The Chrome Dino Game On Another
Upload the code and calibrate the sensor and you’re ready to try it out. Startup the player Arduino first and then start the game on the other. Press the reset button on the LCD shield to start a new game.
It initially looks like the sensor is making the dinosaur jump a little too early, but it doesn’t hit the cactuses and you need to it respond quickly later on in the game. It starts to look better as the game progresses.
You may need to make a few adjustments to the servo travel limits and to the cactus detection set points in your code to get it to work correctly.
Also, position the LDR as close to the LCD as possible. These LDRs are really sensitive, I noticed a significant fluctuation in the measured values with me moving around the room or at different times of the day, so It would be a good addition to add a pot to adjust the set point at any time. This way you could get it to work in any light conditions and use it on the Arduino game or your computer without having to change the code.
Let me know in the comments if you’ve tried playing the Chrome Dino Game on an Arduino or tried getting an Arduino to play the Chrome Dino Game.
If you’ve been kept busy playing the Chrome Dino Game in your browser, how about loading it onto an Arduino to leave on your desk? It’s a really simple game which runs quite well on an Arduino Uno using a simple LCD keypad shield without any other hardware.
I didn’t code this game, it’s a version I found on hackster.io which seems to run the best. It’s written in AVR C code, so it looks a bit different to the generic Arduino language but you should be able to figure a lot of it out and you can still open, edit and upload it using the Arduino IDE.
Here’s a video of the Chrome Dino Game being played on an Arduino:
First, start off by plugging your LCD keypad shield onto your Arduino Uno. Make sure that all of the pins are lined up correctly so that you don’t bend any of them.
Next, plug the Arduino into your computer and start up the Arduino IDE. Make sure that the correct board is selected and that you’re working on the correct com port. Then upload the below sketch. You can also change a couple of things in the sketch if you like, I’ve outlined these after the sketch.
Here’s a brief overview of the game:
The game speeds up the longer you play it.
Cactuses are initially separated by a minimum of 5 spaces and this goes down to a minimum of 3 as the game progresses.
Cheating by holding down the button or continuously pressing the button is prevented.
The current score is displayed during the game and the high score at the end.
The high score is not saved and is lost when power to the Arduino is removed.
Let’s have a look at the code:
//From Hackster.io
//By BRZI
#include <avr/io.h>
#define F_CPU 16000000UL //Our CPU speed (16MHz)
#include <util/delay.h> //Libraries for delay and interrupt utilities
#include <avr/interrupt.h>
#define command 0 //explained in dispSend() function
#define write 1
uint8_t upperBuff[16] , downerBuff[16], overMsgUpper[] = "Score: ", overMsgDowner[] = "Best: ", scoremsg[] = "Score:" , din[] = {0x0E, 0x17, 0x1E, 0x1F, 0x18, 0x1F, 0x1A, 0x12}, cact[] = {0x04, 0x05, 0x15, 0x15, 0x16, 0x0C, 0x04, 0x04};
//Buffers for line one and two. Message to display after lost game. //Score text during game. //Dinosaur and cactus bitmaps
uint8_t canup = 1, longhold = 0, distance = 6, speed = 200, isup = 0, dontprint = 0; //All of these are explained further
uint16_t aVal = 0, score = 1, bestscore = 0;
int i;
void dispInit();
void dispWrite(uint8_t bits);
void dispSend(uint8_t bits, uint8_t act);
void dispSetLine(uint8_t line);
void dispClear();
void dispHome();
void dispPrintChar(uint8_t chr[], uint8_t size);
uint16_t aRead();
int main(void)
{
for(i = 0; i < 17; i++) downerBuff[i] = ' '; //Initialize upper and downward buffer
for(i = 0; i < 17; i++) upperBuff[i] = ' ';
dispInit(); //Initialize the display
TCCR1B |= (1 << WGM12) | (1 << CS11); //Set Timer1 to compare to OCR1A and prescaler of 8
OCR1AH = (500 >> 8); //This equals to 2000Hz or 500us timing, look for TIMER1_COMPA_vect down below
OCR1AL = 500;
TIMSK1 |= (1 << OCIE1A); //Enable Timer1 CompA ISR
sei(); //Enable global interrupt
ADMUX = (1 << REFS0); //Set AREF to VCC
ADCSRA = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADEN); //set ADC prescaler to 128 and enable ADC (defaulted to free running mode)
while (1) {
ADMUX |= (1 << MUX2) | (1 << MUX0); //Set pin from ADMUX to ADC5 (floating)
srand(aRead()); //Use it as a random seed
ADMUX &= ~(1 << MUX2) & ~(1 << MUX0); //Revert back to ADC0 to read the button value
if(aRead() > 900) longhold = 0; //Reads if Up button has been released to prevent cheating. The value is so low because if you hold your fingers beneath one of the buttons the voltage would drop, this prevents the dinosaur from locking up
for(i = 0; i < 16; i++) downerBuff[i] = downerBuff[i + 1]; //Shifts everything in downward buffer by one place to the left
if((rand() % 100) > (rand() % 100) && !dontprint){ //This portion decides if it should put a cactus or a blank spot, dontprint is used to prevent cactus grouping
downerBuff[15] = 0x01; //0x01 represents the cactus (we added cactus and dinosaur to CGRAM when we initialized the display)
dontprint = 1; //This part acts both as a boolean and a counter to ensure cactus separation
}
else downerBuff[15] = ' ';
char lastchar = downerBuff[3]; //We remember the whats initially added to the downward buffer before replacing it with the dinosaur
if(!isup){ //If din should be placed down
downerBuff[3] = 0x00; //Place it down
dispSetLine(2);
dispPrintChar(downerBuff, sizeof(downerBuff)); //Draw it
downerBuff[3] = lastchar; //Place back previous thing to the buffer
canup = 1; //This flag is used to disable dinosaur from getting up before it was drawn down, in this case he can go up
} else { //If din should be placed up
upperBuff[3] = 0x00; //Place it up in upper buff
dispSetLine(1);
dispPrintChar(upperBuff, sizeof(upperBuff));
dispSetLine(2);
dispPrintChar(downerBuff, sizeof(downerBuff)); //Draw it
canup = 0; //In this case he wont go up until rendered on line 2
}
if(dontprint) dontprint++;
if(dontprint > distance) dontprint = 0; //This is the part that ensures cactus separation, it will keep the cactus 3-5 spaces apart minimally (depends on the game progress)
if(isup) isup++; //This part makes sure din is on upper side for 3 loops after he was initially drawn there
if(isup > 4){
upperBuff[3] = ' ';
dispSetLine(1);
dispPrintChar(upperBuff, sizeof(upperBuff));
isup = 0;
}
for(i = 0; i < sizeof(scoremsg); i++) upperBuff[i + 5] = scoremsg[i]; //This part prints the current score during the game
uint8_t cnt = 11;
for(i = 10000; i > 0; i /= 10){
upperBuff[cnt] = ((score / i) % 10) + '0';
cnt++;
dispSetLine(1);
dispPrintChar(upperBuff, sizeof(upperBuff));
}
score++; //Increment the score once on loop
if(score > bestscore) bestscore = score; //Remember best score
if(lastchar == 0x01 && !isup){ //Check if the dinosaur is downward and hit a cactus
dispClear(); //Clear the display and buffers
for(i = 0; i < 17; i++) downerBuff[i] = ' ';
for(i = 0; i < 17; i++) upperBuff[i] = ' ';
uint8_t cnt;
dispSetLine(1);
for(i = 0; i < sizeof(overMsgUpper); i++) upperBuff[i] = overMsgUpper[i]; //Display worst and best score
cnt = sizeof(overMsgUpper) - 1;
for(i = 10000; i > 0; i /= 10){
upperBuff[cnt] = ((score / i) % 10) + '0';
cnt++;
}
dispPrintChar(upperBuff, sizeof(upperBuff));
dispSetLine(2);
for(i = 0; i < sizeof(overMsgDowner); i++) downerBuff[i] = overMsgDowner[i];
cnt = sizeof(overMsgDowner) - 1;
for(i = 10000; i > 0; i /= 10){
downerBuff[cnt] = ((bestscore / i) % 10) + '0';
cnt++;
}
dispPrintChar(downerBuff, sizeof(downerBuff));
while(1){ //Wait for select button to be pressed
aVal = aRead();
if(aVal > 635 && aVal < 645){ //After that clear all the variables
for(i = 0; i < 17; i++) downerBuff[i] = ' ';
dispSetLine(1);
dispPrintChar(downerBuff, sizeof(downerBuff));
for(i = 0; i < 17; i++) upperBuff[i] = ' ';
dispSetLine(2);
dispPrintChar(upperBuff, sizeof(upperBuff));
dontprint = 0;
isup = 0;
score = 1;
speed = 200;
longhold = 0;
distance = 6;
canup = 1;
break;
}
}
}
if(score % 5 == 0) speed -=2; //If score is divisible by 5 make game faster by -2ms
if(speed < 120) speed = 120; //Minimal time in ms (+ ~2ms) that the loop will be halted for (limited by display refreshing, in my testing 11.8Hz was readable enough to be playable)
if(score % 175 == 0) distance--; //Every time you score a number divisible by 175 minimal cactus distance gets smaller
if(distance < 3) distance = 3;
for(i = 0; i < speed; i++) _delay_ms(1); //This is the only way as the compiler expects a const number here
}
}
void dispInit(){
_delay_ms(50); //Just in case
DDRD = 0b11110000; //Set these pins to output. PD4 - PD7 correspond to D4 - D7 on display, we need to configure it to run in 4 bit mode
DDRB = 0b00000011; //PB0 is tied to RS and PB1 to EN
dispWrite(0x30);//*This part here is explained in Hitachi HD44780 datasheet on how to initialize the display in 4bit mode
_delay_us(4500);//*Essentially you send the reset signal 3 times, and then set it to 4 bit mode
dispWrite(0x30);//*
_delay_us(4500);//*
dispWrite(0x30);//*
_delay_us(4500);//*
dispWrite(0x28);//*
dispSend(0x28, command); //Send 4bit mode function set
dispSend(0x08, command); //Turn the display off
dispSend(0x01, command); //Clear its RAM (if MCU resets that doesn't mean the display was reset, so we clear everything)
_delay_ms(50);
dispSend(0x0C, command); //Turn the display on
_delay_ms(5);
dispSend(0x40, command); //Tell the display we want to enter a custom character to its CGRAM (on address 0x00)
for(i=0; i<8; i++) dispSend(din[i], write);
dispSend(0x80, command); //Transaction end
dispSend(0x48, command); //Same thing, but for 0x01
for(i=0; i<8; i++) dispSend(cact[i], write);
dispSend(0x80, command);
}
void dispPrintChar(uint8_t chr[], uint8_t size){
for(uint8_t i = 0; i < size; i++) dispSend(chr[i], write); //Self explanatory
}
void dispSetLine(uint8_t line){
if(line == 2) dispSend(0xC0, command); //Sets the line where 0xC0 is line 2 and 0x80 is line 1
else dispSend(0x80, command);
}
void dispClear(){
dispSend(0x01, command); //Self explanatory
_delay_ms(2); //This command takes longer for the IC to process, this delay is necessary
}
void dispHome(){ //This function isn't used in this application but its there for expandability, it places the cursor on the line 1 column 1
dispSend(0x02, command); //Self explanatory
_delay_ms(2);
}
void dispSend(uint8_t bits, uint8_t act){
if(act) PORTB |= (1 << DDB0); //Set PB0 if we are writing a character, else pull it low
else PORTB &= ~(1<<DDB0);
dispWrite(bits); //Send the bit then shift them 4 bit to the left to work in displays 4bit mode
dispWrite(bits << 4);
_delay_us(80);
}
void dispWrite(uint8_t bits){
PORTD = bits; //This is a dirty way to write it but it's perfect for this application as it's not bulky and PORTD isn't used for anything else anyway
PORTB |= (1<<DDB1); //Pulse the PB1 to signal the IC to read the data
_delay_us(1);
PORTB &= ~(1<<DDB1);
_delay_us(1);
}
uint16_t aRead(){
ADCSRA |= (1 << ADSC); //This signal the avr to read the ADC value
while (ADCSRA & (1 << ADSC)); //Wait until it's finished
return ADCL | (ADCH << 8); //Send it back stitched together
}
ISR (TIMER1_COMPA_vect){ //Timer ISR we set up earlier
if(!longhold){ //Return if the Up button was still held
aVal = aRead(); //Read from ADC0
if(aVal > 450 && aVal < 600 && canup){ //Check if Up is pressed and that din was rendered down
isup = 1;
longhold++;
}
}
}
There are a couple of things you can edit to customise the game without knowing AVC C code, here are some of them:
Line 11 – Change the score/high score screen text.
Line 140 – Change by how much the speed increases every score increment of 5, or change the increment.
Line 141 – Change the maximum speed limit. The original coder suggests nothing less than 120ms a cycle or it becomes unplayable on the LCD.
Line 142 – Change how often the cactus spacing gets closer together.
Line 143 – Change the minimum cactus distance.
The game is quite easy to play on the LCD keypad shield although the buttons are not the best for quick presses and the LCD is quite slow, so it starts suffering from ghosting and brightness issues once the cactuses start moving quickly.
Let me know if you’ve loaded this game onto your Arduino Uno and what you’ve changed on it. Enjoy playing it!
Not very many people enjoy cleaning, so we’re all looking for ways to make cleaning a bit quicker and easier. Here’s a list of our favourite cleaning shortcuts for 2020 to help you get your home looking great without too much time and effort.
Slip a paper towel tube or toilet roll over the end of your vacuum hose. This allows you to squash the end of the tube to fit into all those gaps which were previously hard to reach, think window and door sliders, between the couch cushions and in the corners of cupboards and drawers. The cardboard also won’t scratch or damage your trim, walls or furniture.
Clean your silver jewellery and cutlery with baking soda, salt and aluminium foil. The reaction removes the silver tarnish, doesn’t damage your jewellery and doesn’t require any rubbing or brushing.
Are your glasses and cutlery coming out of the dishwasher cloudy? Most people think that this is because they’re not using enough rinse aid or salt. If you use tablets in your dishwasher then its more likely that you’re using too much rinse aid. These tablets are typically designed to be “all in one” so they don’t require you to add rinse aid and salt to your machine as well. In fact, some machines ask you if you are using a tablet and then do not dose extra rinse aid into the water.
Hang a soap dispensing dish brush in your shower. Instead of having to give your shower a big clean every week, hang a dish brush with some liquid in it in your shower and get into a habit of giving the shower a quick clean after every use. It’ll still be hot and the grime won’t have dried in place so it’ll be quicker and easier to keep clean in the long run.
If you find your laundry detergent cap getting sticky and messy from the liquid, especially when you buy larger containers, just throw the cap in with your next load. It’ll come out looking new again and requires no effort from you to clean.
Ice cubes help lift furniture imprints from your carpets. Simply place a cube on the indent and let it melt. Use some paper towel to absorb the water and allow it to dry and your carpet will be as good as new again.
A cup of baking soda will deodorise your closet. Simply place an open cup of baking soda into the corner of your cupboard to absorb any moisture and reduce that musty or damp smell. Also, have a look at these other baking soda hacks which you need to try.
Coffee grounds make a great eco-friendly and gentle scourer to clean your pots and pans. Use a tablespoon of spent coffee grounds to make washing your dirty pots and pans a breeze. You can also use your spent coffee grounds in the garden as a natural fertilizer.
Now that you’ve got some shortcuts lined up to keep you home clean, you’ll have more time for your next DIY project. Let us know how else you’ve saved time cleaning your home in the comments section below.
In this tutorial, I’m going to be showing you how to connect up to 992 servos to an Arduino by using these 16 channel PCA9685 PWM drivers, which are controlled over the I2C interface. Each one of these boards can drive up to 16 servos or PWM outputs and you can chain up to 62 of them together, meaning that you could drive up to 992 servos – all controlled by just two pins on your Arduino. They’re also relatively inexpensive, there are good quality ones available for around $10 online, but you can also find ones from just $2-$3 each if you’re prepared to wait a bit longer for shipping.
Here’s a video summary of the tutorial and servos in operation, read on for the step by step instructions and the code:
Each board has two sets of control input pins, one on each side. You can use either set of control inputs to connect your board to your Arduino, but they’re primarily useful to chain the drivers together. Each board can be plugged into the pins on the board before it, with the first connected to your Arduino.
Each board also has two power terminals at the top to provide a dedicated 5-6V power supply to the outputs and then the 16 outputs along the bottom. What’s also nice about these boards is that they’re already set up to accommodate the 3 pin servo plugs. So you can plug your servos directly into the board instead of needing additional wiring like you’d need if you were plugging the servo directly into your Arduino.
On the top right of each board is a set of bridgeable address jumpers which allow you to bridge different combinations to create a unique address for up to 62 different boards. To change the address of a board, you just need to solder a bridge across the two address terminals. This changes the address as follows:
You can also use these boards to control LEDs, so you can control 992 LEDs individually and control their brightness as well. Although each channel is completely independent, each board must operate at the same PWM frequency. This means that if you’d like to use a combination of LEDs and servos, you’d likely need to split them up across two boards because servos typically operate at 50Hz and LEDs at 1kHz. Also, keep in mind that the maximum current output per pin is 25mA and there are already 220 home resistors in series with all of the PWM pins.
Have a look at this 7 segment display clock which was built using these PCA9685 servo drivers.
Connecting The Boards Together & To Your Arduino
To chain two boards together, we’ll need to add some pins to the right side of the board to plug the next board into.
We’ll then bridge the first terminals on the right to change the address on the second board so that our Arduino can differentiate between the two.
Looking at the back of the board, the board accepts two supply voltages, one between 3 and 5 volts for the logic or onboard chip and the second up to 6V which is to supply the output pins. There is also a note to say that the terminals at the top have reverse polarity protection while the input on the side through the control pins does not.
The power supplied through the terminals is fed to the V+ control pin, meaning that you don’t need to connect every board in the chain through the terminals, unless you’re drawing a lot of current on each.
Now that we’ve added the second set of control pins and changed the address on the second board, we need to create a ribbon cable connector to join the two boards together. You only need to connect 4 pins to your Arduino and between each board, the ground, the two I2C pins and the logic supply voltage VCC. The other two pins, OE and V+ are used to enable or disable the boards and to supply voltage to the output pins. I’ve created ribbon cable to connect all six pins between the boards so that the output voltage is fed to the second board and so that I can use the enable pin in future if needed.
When connecting your PWM drivers to your Arduino, make sure that you use the correct I2C pins.
This is through analogue pins 3 and 4 on older Arduino Uno’s, through digital pins 20 and 21 on older Megas and through the dedicated SDA and SCL pins on later model Unos, Megas and Leonardos. Remember that Vcc is only to supply the logic circuits on the drivers, not the outputs. To drive the outputs it is recommended that you use a dedicated supply through the terminals.
Now go ahead and plug your servos into the output pins on your boards. Make sure that the plugs are put in the correct way around, with the brown wire being ground and the orange wire being the PWM signal.
Programming Your Arduino To Control The Servos
Now let’s have a look at the code and how to control each servo.
We’re going to be using the Adafruit PWM servo driver library. You can install this library easily from the Arduino IDE by going to Sketch -> Include Libraries -> Manage Libraries then searching for Adafruit PWM and clicking install.
Once the library is installed, we can write a simple program to move each of the 6 servos on each board individually.
//Michael Klements
//The DIY Life
//3 May 2020
#include <Adafruit_PWMServoDriver.h> //Include the PWM Driver library
Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x40); //Create an object of board 1
Adafruit_PWMServoDriver pwm2 = Adafruit_PWMServoDriver(0x41); //Create an object of board 2 (A0 Address Jumper)
int servoMin = 150; // This is the servos minimum pulse length count (out of 4096)
int servoMax = 600; // This is the servos maximum pulse length count (out of 4096)
int servoFrequency = 50; // Servo update frequency, analog servos typically run at ~50 Hz
void setup()
{
pwm1.begin(); //Start each board
pwm2.begin();
pwm1.setOscillatorFrequency(27000000); //Set the PWM oscillator frequency, used for fine calibration
pwm2.setOscillatorFrequency(27000000);
pwm1.setPWMFreq(servoFrequency); //Set the servo operating frequency
pwm2.setPWMFreq(servoFrequency);
}
void loop()
{
for (int i=0 ; i<=5 ; i++) //Cycle through moving 6 servos on each board
{
for (int pulseLength = servoMin ; pulseLength <= servoMax ; pulseLength++) //Move each servo from servoMin to servoMax
{
pwm1.setPWM(i, 0, pulseLength); //Set the current PWM pulse length on board 1, servo i
pwm2.setPWM(i, 0, pulseLength); //Set the current PWM pulse length on board 2, servo i
delay(1);
}
delay(100);
for (int pulseLength = servoMax ; pulseLength >= servoMin ; pulseLength--) ////Move each servo from servoMax to servoMin
{
pwm1.setPWM(i, 0, pulseLength); //Set the current PWM pulse length on board 1, servo i
pwm2.setPWM(i, 0, pulseLength); //Set the current PWM pulse length on board 2, servo i
delay(1);
}
delay(100);
}
delay(500);
}
We then create a new object for each of the connected boards, remembering to change the address to suite the address jumpers we’ve used.
We then set a minimum and maximum travel limits for our servos. This is quite important so that you’re not over travelling your servos which may cause them to burn out or strip the gears. We also set the servo operating frequency. Most analogue servos run at 50hz.
In the setup function, we start each board then set the oscillator frequency and the servo frequency.
In the loop function, we’ve got a loop which cycles through the six servo numbers, numbered from 0 to 5 as per the board output numbers.
We then have a loop which drives the current servos, one on each board, from their minimum position to their maximum position with a 1ms delay between movements, which is a relatively slow servo movement.
We then wait 100 milliseconds and move the same two servos from their maximum position back down to their minimum position at the same speed.
We then wait another 100 milliseconds before moving onto the next two servos.
Once all 6 servos have been moved on each board, we wait 500 milliseconds and then start again moving the first two servos and the loop continues.
Let’s upload the sketch and have a look at the servos moving.
Being able to control servos like this enables you to build more complex projects by freeing up your Arduino’s IO and reducing the processing load because these servo drivers produce their own PWM signal. These are great for building robot arms, walking robots and projects involving a lot of LEDs, such as cubes, clocks and simple games.
Let me know in the comments section below what you plan on using these drivers for.
In this teardown video, I’ve taken an iPhone X apart as far as possible with a standard repair toolkit and without breaking any of the components open. I’ve also included some close-up shots of some of the more interesting components to have a look at, have a look at the stills after the video.
Here are some of the still images of the internal components of the iPhone X from the teardown:
These ribbon cable connectors connect the various components in the iPhone together, making repairs and replacements of parts easier. Older model mobile phone’s used to have soldered connections, which required a lot of effort to replace components.
The Taptic Engine is an electromagnetic driver which produces the vibrations you feel when your iPhone rings, for feedback when clicking on or moving items and icons and when you pressed the home button on older iPhone 7 and 8 models – the ones which had fixed home buttons which didn’t “click” when pressed when the iPhone was off.
The dual camera assembly on the iPhone X, featuring a wide-angle, optically stabilised f/1.8 camera as seen on previous iPhone generations and a newer telephoto lens for close up and portrait shots.
FaceID was a new addition to the iPhone X, providing users with a means to securely unlock their iPhone’s without having to physically press a button or fingerprint sensor.
The logic board is the heart of every iPhone. This is the assembly which houses the processor and control boards which drive the entire iPhone, all of the peripheral devices, such as the speakers, cameras, buttons and battery plug into this board. It’s incredible how small this board is considering what it contains and how much it can do.
On the back of the display is a small white sticker, this is the water damage indicator which is visible through the SIM card tray. This indicator turns red permanently when it comes into contact with water, indicating possible water damage to the internal components. You can have a look at where it is in the SIM card tray and how it works in our previous article.
Lastly, here’s a close up of the inside portion of the lightning connector – the only port left on modern iPhones.
Hope you enjoyed the teardown! Have you ever opened your iPhone up to repair it? Let us know what repairs you’ve done in the comments section below.