Arduino Smartwatch

Over the last few years, various wearable technology have been created by various IT companies. VR headsets, smartwatches are some of the few examples. Smartwatches have become more and more popular as it provides the platform for the user to check on notifications on their phone without whipping out their phone. It has become a convenient gadget for a lot of people. Although more affordable models exist, what’s the fun of just buying one when you can make one for less than $40? Today I will be showing you how I made mine that connects to an Android Phone and shows SMS notifications and syncs time with an android smartphone.

Step 1: Parts needed:

1. 0.96″ Or 1.3″ SSD1306/SH1106 SPI/I2C 128×64 OLED Screen (From ebay)

2. An Arduino Pro Mini 3.3V with ATmega328P (From ebay)

3. An Arduino Uno or a TTL-Level 3.3V USB to Serial Converter (From ebay)

4. HC06/HC05 Bluetooth Module (I recommend using HC-06) (From ebay)

5. Wires (as small as possible. I used wire wrapping wires)

6. A small Li-Po battery below 500mAh (Choose according to your size preference, I used 600mAh) (From local shop)

7. Small Push Buttons

8. A TP4056 Li-Ion Battery Charging module (I recommend the one with the battery protection circuit.) (From ebay)

9. An Android Phone

If you need the ebay links don’t hesitate to comment below and I will add the links for you.

Parts

Step 2: Before we do anything, we must make sure that everything works by prototyping on the breadboard. I recommend using an Arduino Uno as it is more convenient when prototyping and similar to its mini counterpart, the Arduino pro mini, except it is 3.3V. If you’re using a bare HC05/HC06 without a breakout board, you should use resistors to step down the I/O voltage to 3.3V on the VCC and RX line of the Bluetooth module. (I recommend using 330 ohms and 1kohm. The centre pin between the 2 resistors is the step-downed voltage) Same for the OLED. However most SSD1306/SH1106 OLEDs are 5V tolerant. Check with the manufacturer/seller for more info.

Breadboard Circuit

Arduino_Smartwatch_Schematic

Firstly, I planned the watch face using Adobe’s Illustrator. I made a new file with its width set to 128 px and height to 64px, representing each pixel on the OLED display. I then used the U8glib library to code the drawings on the OLED. At that time, I temporarily used the millis() function to increase the time from 0. Next was the menu system. I had originally planned to use 4 buttons for the watch (1 for next, 1 for previous, 1 for menu, 1 for select) but I figured out its too many. 3 would be enough. I combined the menu button into next and previous button. When both buttons are pressed, it would go to the menu. Similarly, I used Adobe Illustrator to plan the menu.

Adobe Illustrator Drawing

OLED_Menu

The illustrator drawing is available for download below.

 

The menu system works based on another separate function. Once the user presses the select button, the function would return a number, for example ‘2’ would mean the user selected the apps menu. The void loop function would then run the app menu function. Next is to program the most important part of the watch, the bluetooth sync SMS and time sync capability. I actually developed the android app using MIT’s App Inventor 2 first before I start on the Arduino side. I will explain more on the app on the next section. For the SMS part, I did the coding on a separate program so as to make it easier and not confusing. After I have made it working, only then I would combine both of them together. Test the arduino program by pushing both buttons that are connected to pin 7 and 5. It should go on to a menu. Pin 4 is the select menu option. If it does, you have done the connection correctly.

 

Block Diagram

Step 3: The Android App I used MIT’s App Inventor 2 to develop the app that interfaces with the watch as it only involves dragging and dropping blocks. I utilised the Texting and bluetoothClient modules. Basically how it works is that the texting component will receive and store the text message into a String variable. It will pass the variable to the bluetoothClient component which will affix a ‘2’ to the front of the string so that the arduino can identify that this string is a text message. As for the time sync function, I used the clock component to get the current time of the phone. It will then pass it to the bluetoothClient component which will then affix a ‘1’ to the front of the string so that the arduino can identify that it is a string of characters that represent time. Test the app by first installing it on your phone, and then connect to your HC05/06 by selecting connect and then selecting your HC05/06. Note: You have to pair your HC05/06 to your phone in your android settings menu for your phone before you can connect it in the app. Make sure your bluetooth is turned on. You can download the app (apk and .aia appInventor file) in the end of this post.

App Inventor

Step 4: Soldering everything together

Firstly I removed the reset button on the pro mini as it might be accidentally pressed when the watch is assembled. Also, it is the tallest component on the pro mini. I heated up one of the pads and then used tweezers to lift the button up. Then I heated up the other side and lifted up the whole reset button.

The next thing I did was to remove the header pins on the OLED screen. The best way to remove it is to heat up one of the pins, and then slowly lift up the pin one by one when they become loose. After all of the pins are lifted, the plastic part of the header can then be lifted up easily. After that, I used short wire wrapping wires to solder. Remember, the objective is to use as little wire as possible as wires can.

Step 5: 3D printed casing + watch strap

I plan to use the ninjaflex flexible filament for the casing. My friend suggested that we print a small rectangular sample for the strap so that we can test the flexibility of the ninjaflex strap as this is our first time using it. Since it was recommended that the print speed for the ninjaflex filament set at 30mm/s, we had it set to 20mm/s, just to be cautious. We used the school’s Makerbot Replicator 2 with the spring loaded extruder. Originally the school’s makerbot had a plastic build platform, but then it was replaced with a glass one with flashforge’s pre cut print tape. There was a bit of imperfection on one side, but that is due to the unleveled build plate.

20150703_153221

20150703_153349

As for drawing of the casing, we at first modeled all the parts including the OLED, HC06, Pro mini and TP4056 charger board in CAD (Autodesk Inventor). Then we assembled it in CAD and then only we modeled the casing around it.

 

Links/File Downloads

Arduino Code

Android App (APK)

App Inventor File (.aia)

Illustrator Template

Current Prototype

Prototype

Video Demo

Advertisements

23 thoughts on “Arduino Smartwatch

    1. Hi Yajat,

      You could look up in the u8glib reference page and compare to the snake game code you have right now. You would have to change the functions in the Adafruit GFX Library for a similar one to u8glb.

      For example,
      display.drawPixel(X,Y,WHITE); (Adafruit GFX) –> u8g.drawPixel(X,Y); (u8glib)
      display.drawLine(X1,Y1,X2,Y2,WHITE); (Adafruit GFX) –> u8g.drawline(X1,Y1,Y1,Y2); (u8glib)
      display.print(“String”); (Adafruit GFX) –> u8g.drawStr(xPos,yPos,”String”); or u8g.print(“String”); (u8glib)

      Let me know if you face any problems

      Like

    1. Thanks for your comment, it is pretty easy, just that my code is a bit messy…
      The below instructions is for putting an option to run your code under “Apps” in the menu.

      1) Go to line 524 and uncomment this line. Change the string to whatever you want, to maybe “Snake”. This instructs u8Glib to draw a text for your menu option

      2) Go to line 472 and 476 and change the “menuSelectiona” to 4. This allows the menu selection bar to go up to 4, instead of 3 (1st 3 options is “Return,Torch,SMS”). 4 will represent the snake game

      3) Now go to line 319 and add this

      if(menuSelectiona == 4)
      {
      //Insert Snake code/function in here
      }

      This is where your snake code will run.

      This should work in adding your snake game. Let me know if it works. I also added in a video that I found in my PC from years ago that I didn’t came to uploading it at that time. Maybe that could help you in your coding.

      Cheers😊

      Like

      1. I have this code please tell what changes I have to make in this code to make it work thanks ….sorry for disturbing

        #if (SSD1306_LCDHEIGHT != 64)
        #error(“Height incorrect, please fix Adafruit_SSD1306.h!”);
        #endif

        const int D_NORTH = 1;
        const int D_EAST = 2;
        const int D_SOUTH = 3;
        const int D_WEST = 4;

        long score = 0;

        byte snakePixelX[20];
        byte snakePixelY[20];

        int snakeX = 10;
        int snakeY = 30;
        int snakeLength = 1;
        volatile int snakeDir = D_NORTH;
        volatile int buttonState = LOW;

        int minX = 0;
        int minY = 20;
        int maxX = 128;
        int maxY = 64;

        int foodX = 0;
        int foodY = 0;

        const int BUTTON_PIN = 7;

        void setupButton() {
        attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), debounceChangeDirection, CHANGE);
        }

        long debouncing_time = 15; //Debouncing Time in Milliseconds
        volatile unsigned long last_micros;

        void debounceChangeDirection() {
        if((long)(micros() – last_micros) >= debouncing_time * 1000) {
        changeDirection();
        last_micros = micros();
        }
        }

        void changeDirection() {
        switch(snakeDir) {
        case D_NORTH:
        snakeDir = D_EAST;
        break;
        case D_EAST:
        snakeDir = D_SOUTH;
        break;
        case D_SOUTH:
        snakeDir = D_WEST;
        break;
        case D_WEST:
        snakeDir = D_NORTH;
        break;
        }

        buttonState = !buttonState;
        }

        void setupScreen() {
        // by default, we’ll generate the high voltage from the 3.3v line internally! (neat!)
        display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128×64)
        // init done

        display.clearDisplay();

        minX = 0;
        minY = 8;
        maxX = display.width()-1;
        maxY = display.height()-1;

        renderScore();
        }

        void setup() {
        setupButton();
        setupScreen();

        dropFood();

        digitalWrite(BUTTON_PIN, buttonState);
        }

        bool outOfBounds() {
        return snakeX <= minX || snakeY = maxX || snakeY >= maxY;
        }

        void gameOver() {
        display.clearDisplay();
        display.setCursor(5, 5);
        display.print(“GAME OVER :(“);
        display.display();

        score = 0;
        snakeLength = 1;
        snakeX = display.width() / 2;
        snakeY = display.height() / 2;

        snakePixelX[snakeLength-1] = snakeX;
        snakePixelY[snakeLength-1] = snakeY;

        snakeDir = D_SOUTH;

        delay(2000);

        display.clearDisplay();
        renderScore();
        }

        // Drop food on random location
        void dropFood() {
        foodX = random(minX+1, maxX-1);
        foodY = random(minY+1, maxY-1);
        }

        bool collectFood() {
        if (snakeX == foodX && snakeY == foodY) {
        score += 10;

        tone(8, 1000, 10);
        tone(8, 1020, 10);
        tone(8, 1040, 10);
        tone(8, 1000, 10);
        tone(8, 1000, 10);

        renderScore();
        dropFood();

        return true;
        } else {
        return false;
        }
        }

        void renderScore() {
        display.fillRect(0, 0, display.width()-1, 8, BLACK);

        display.setTextSize(1);
        display.setTextColor(WHITE);
        display.setCursor(0,0);

        display.print(“Score: “);
        display.print(String(score, DEC));

        // TOP
        display.drawLine(0, 8, display.width()-1, 8, WHITE);
        // LEFT
        display.drawLine(0, 8, 0, display.height()-1, WHITE);
        // RIGHT
        display.drawLine(display.width()-1, 8, display.width()-1, display.height()-1, WHITE);
        // BOTTOM
        display.drawLine(0, display.height()-1, display.width()-1, display.height()-1, WHITE);
        }

        bool crashedIntoSelf() {
        for(byte i = 4; i < snakeLength; i++) {
        if (snakeX == snakePixelX[i] && snakeY == snakePixelY[i]) {
        return true;
        }
        }

        return false;
        }

        void drawScreen() {
        bool foodCollected = false;

        // Clear the buffer.
        display.clearDisplay();

        display.drawPixel(foodX, foodY, WHITE);
        foodCollected = collectFood();

        // Check snake position
        if (outOfBounds() || crashedIntoSelf()) {
        gameOver();
        }

        // Render the snake
        for(int i = 0; i 0; i–) {
        snakePixelX[i] = snakePixelX[i-1];
        snakePixelY[i] = snakePixelY[i-1];
        }

        if (foodCollected) {
        snakeLength += 1;
        snakePixelX[snakeLength-1] = snakeX;
        snakePixelY[snakeLength-1] = snakeY;
        }

        switch(snakeDir) {
        case D_NORTH:
        snakeY -= 1;
        break;
        case D_EAST:
        snakeX += 1;
        break;
        case D_SOUTH:
        snakeY += 1;
        break;
        case D_WEST:
        snakeX -= 1;
        break;
        }

        snakePixelX[0] = snakeX;
        snakePixelY[0] = snakeY;

        renderScore();
        display.display();
        }

        void loop() {

        drawScreen();

        }const int D_NORTH = 1;
        const int D_EAST = 2;
        const int D_SOUTH = 3;
        const int D_WEST = 4;

        long score = 0;

        byte snakePixelX[20];
        byte snakePixelY[20];

        int snakeX = 10;
        int snakeY = 30;
        int snakeLength = 1;
        volatile int snakeDir = D_NORTH;
        volatile int buttonState = LOW;

        int minX = 0;
        int minY = 20;
        int maxX = 128;
        int maxY = 64;

        int foodX = 0;
        int foodY = 0;

        const int BUTTON_PIN = 7;

        void setupButton() {
        attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), debounceChangeDirection, CHANGE);
        }

        long debouncing_time = 15; //Debouncing Time in Milliseconds
        volatile unsigned long last_micros;

        void debounceChangeDirection() {
        if((long)(micros() – last_micros) >= debouncing_time * 1000) {
        changeDirection();
        last_micros = micros();
        }
        }

        void changeDirection() {
        switch(snakeDir) {
        case D_NORTH:
        snakeDir = D_EAST;
        break;
        case D_EAST:
        snakeDir = D_SOUTH;
        break;
        case D_SOUTH:
        snakeDir = D_WEST;
        break;
        case D_WEST:
        snakeDir = D_NORTH;
        break;
        }

        buttonState = !buttonState;
        }

        void setupScreen() {
        // by default, we’ll generate the high voltage from the 3.3v line internally! (neat!)
        display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128×64)
        // init done

        display.clearDisplay();

        minX = 0;
        minY = 8;
        maxX = display.width()-1;
        maxY = display.height()-1;

        renderScore();
        }

        void setup() {
        setupButton();
        setupScreen();

        dropFood();

        digitalWrite(BUTTON_PIN, buttonState);
        }

        bool outOfBounds() {
        return snakeX <= minX || snakeY = maxX || snakeY >= maxY;
        }

        void gameOver() {
        display.clearDisplay();
        display.setCursor(5, 5);
        display.print(“GAME OVER :(“);
        display.display();

        score = 0;
        snakeLength = 1;
        snakeX = display.width() / 2;
        snakeY = display.height() / 2;

        snakePixelX[snakeLength-1] = snakeX;
        snakePixelY[snakeLength-1] = snakeY;

        snakeDir = D_SOUTH;

        delay(2000);

        display.clearDisplay();
        renderScore();
        }

        // Drop food on random location
        void dropFood() {
        foodX = random(minX+1, maxX-1);
        foodY = random(minY+1, maxY-1);
        }

        bool collectFood() {
        if (snakeX == foodX && snakeY == foodY) {
        score += 10;

        tone(8, 1000, 10);
        tone(8, 1020, 10);
        tone(8, 1040, 10);
        tone(8, 1000, 10);
        tone(8, 1000, 10);

        renderScore();
        dropFood();

        return true;
        } else {
        return false;
        }
        }

        void renderScore() {
        display.fillRect(0, 0, display.width()-1, 8, BLACK);

        display.setTextSize(1);
        display.setTextColor(WHITE);
        display.setCursor(0,0);

        display.print(“Score: “);
        display.print(String(score, DEC));

        // TOP
        display.drawLine(0, 8, display.width()-1, 8, WHITE);
        // LEFT
        display.drawLine(0, 8, 0, display.height()-1, WHITE);
        // RIGHT
        display.drawLine(display.width()-1, 8, display.width()-1, display.height()-1, WHITE);
        // BOTTOM
        display.drawLine(0, display.height()-1, display.width()-1, display.height()-1, WHITE);
        }

        bool crashedIntoSelf() {
        for(byte i = 4; i < snakeLength; i++) {
        if (snakeX == snakePixelX[i] && snakeY == snakePixelY[i]) {
        return true;
        }
        }

        return false;
        }

        void drawScreen() {
        bool foodCollected = false;

        // Clear the buffer.
        display.clearDisplay();

        display.drawPixel(foodX, foodY, WHITE);
        foodCollected = collectFood();

        // Check snake position
        if (outOfBounds() || crashedIntoSelf()) {
        gameOver();
        }

        // Render the snake
        for(int i = 0; i 0; i–) {
        snakePixelX[i] = snakePixelX[i-1];
        snakePixelY[i] = snakePixelY[i-1];
        }

        if (foodCollected) {
        snakeLength += 1;
        snakePixelX[snakeLength-1] = snakeX;
        snakePixelY[snakeLength-1] = snakeY;
        }

        switch(snakeDir) {
        case D_NORTH:
        snakeY -= 1;
        break;
        case D_EAST:
        snakeX += 1;
        break;
        case D_SOUTH:
        snakeY += 1;
        break;
        case D_WEST:
        snakeX -= 1;
        break;
        }

        snakePixelX[0] = snakeX;
        snakePixelY[0] = snakeY;

        renderScore();
        display.display();
        }

        void loop() {

        drawScreen();

        }

        Like

        1. Hi Vashisht,

          I see that your code uses the Adafruit SSD1306 library. However I used u8glib. You would have to convert your code to use u8glib instead of the Adafuit SSD1306 library

          Both libraries should be quite similar in terms of their code. You would also need to use an additional pin for your 4 directions for moving the snake.

          Like

  1. Hi
    Can you send me some images of the blocks for your android app?
    I am building a watch using a BLE chip, so I can’t directly use your application.
    Please help!

    Like

      1. Thanks a lot!
        It’s working fine now.
        I had to change the bluetooth blocks.
        Those commands respond well!
        Although, the sms won’t work; I’ll figure something out.
        Thanks again

        Like

  2. hello
    I am impressed by your watch
    But in your app when my phone screen is off your app don ‘t work
    How i can solve the problem
    Thank you

    From Louis

    Like

        1. Yeah there is no way to remove this limitation. However I heard that there is a new version of Appinventor that allows app to run as a background services

          Like

  3. HI. hassanul.
    Im so impressed by ur app using app inventor2
    Can u let me see App inventor project ? for have a look how to make it ( blocks n components)
    u can Export project file(.aia) use projects menu of app inventor2 website
    if u can send to my e-mail pls
    thank

    from HAN.

    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s