Introduction: Wii Nunchuck Arduino Spirit Level

About: A human wanting to have wings to fly...
Since I have read an article on todbot blog, I bought a couple of Wii Nunchucks from ebay. I don't remember how much I paid for them, but it was much cheaper than buying the accelerometer breakout boards. With the nunchuck data reading library shared by todbot (Thanks!), I could easily read the accelerometer data for a Wii Nunchuck as well as the joystick and two button status.

I have thought about how to use it useful and have tested with two servos, but I had no time to proceed that project which have sit on my desk for a long time.

Recently, when I hang a picture frame on the wall at home, I got an idea to use the Wii Nunchuck more useful.  That is the electronic level like a bubble level (or spirit level) using the accelerometer of the Wii Nunchuck and Arduino compatible board, JeonLab mini v1.3. Why JeonLab mini?  Because 1) it is small enough to integrate into a small case; 2) once the program is loaded, the FTDI interface is not needed; and 3) since I'm going to use a small power supply, I don't need the 5V regulator circuit.  It's a minimal Arduino, so you can easily make one on a small piece of prototype board.

What this Arduino spirit level does is to show whether the surface or edge (picture frame or a table for example) is leveled or not by displaying LEDs.  If it is leveled, the central red LED is lit, otherwise the left or the right LED will turn on depending on which side it is tilted.  It works similar to a bubble level, so if it tilts left the right green LED will turn on.  I like this way but if you don't like, you can modify the program and load it through the FTDI breakout board which can be plugged in at the top right corner of the JeonLab mini board as shown in the picture.

In the later steps, you will find the entire circuit diagram and Arduino codes for this project.

Step 1: Circuit Diagram

Click on "i" button at the top left corner of the diagram for full size view.

The circuit is quite simple.  There is a minimal Arduino compatible board, JeonLab mini v1.3 on the left of the diagram. As you can see, if you have an ATmega328P chip with Arduino bootloader, a 16MHz ceramic resonator, and a few resistors and capacitors can replace it. There is no built in FTDI interface so you need an external FTDI breakout board or FTDI-USB cable to load the program. But that's not a big deal and good to reduce the whole size.

The accelerometer board is from a broken Wii Nunchuck and it can communicate via I2C interface: 3.3V, GND, data pin (SDA) to Arduino analog input pin 4, and the clock pin (SCL) to Arduino analog input pin 5.

The digital pins 5 to 9 are used to illuminate the LEDs to show which direction it is tilted.  The digital pin 10 is normally pulled down through a 10k resistor and goes HIGH when the calibration switch is pressed and connect the pin to V+.

After some trials, I decided to use a 12V A23 size battery and a 3.3V regulator to provide 3.3V to both the accelerometer and the Arduino.

IMPORTANT NOTE ON THE POWER SUPPLY
The power supply I initially thought was 3.0V battery, so I thought sharing the power should be fine. BUT I forgot the program upload through the FTDI. The accelerometer chip and the I2C interface need 3.3V (3.0-3.6V) and the ATmega328 on the JeonLab mini v1.3 (and other Arduino compatible boards as well) can work at 3-5V. The Nunchuck data reading header, nunchuck_funcs.h (from WiiChuckDemo by Tod E. Kurt) provides the settings for utilizing the analog pins 3 and 2 as power source for the Nunchuck board but this provides 5V, not 3.3V. The problem is that 5V supply to the Nunchuck board could damage the chip(s) either the accelerometer or the I2C chip or both. Actually, the first one I used had gotten unstable and noisy after several times of tests, so it had to be replaced with a new one. That’s when I decided to change the power source from the 3V battery to 12V battery with a 3.3V regulator and added a Schottky diode (1N5819) to protect the Nunchuck board from FTDI 5V supply. This way, when the FTDI is connected, the 5V from a USB port ONLY powers the ATmega328P and not the accelerometer board.

Step 2: Parts

As you can see, many parts are from broken electronics. "broken" means it is just not working as it is supposed to.  There are still many parts you can use for other projects.

Step 3: Assemble the Main Part (JeonLab Mini + Wii Nunchuck Accelerometer)

Step 4: Assembly of the Power Supply

Step 5: Assembly in the Case

Step 6: Wii_Nunchuck_Level Arduino Sketch Load

There are two Arduino sketches: 1) Wiichuck_range_test; and 2) Wii_Nunchuck_Level.

First of all, you need to know what the Nunchuck accelerometer's each axis data range are. I have tested a few of them and all of them shows slight differences.  But mostly, the ranges are 60-70 as minimum and 180-200 as maximum.  In order for the best sensitivity of the Level, you need to load the Wiichuck_range_test first and find those values (minimum, center (at level), and maximum) for each axis and adjust the values in the main sketch if necessary.

The range test sketch is simple and straight forward so I won't explain the detail here.

The main program, Wii_Nunchuck_Level is shown below and also attached. 

The function getData() get the values for each axis and store them in byte array accl[] and find the orientation of the Level and returns 'orient.'

The function orientation() get the current axis values and find the orientation of the Level.  Each orientation gives two axes as middle (leveled, around 120-130) and one axis as either minimum or maximum depending on the gravity direction.  Using this characteristics, it determines the orientation.

The main loop() is quite simple.  It monitors if the calibration button is pressed by seeing if the pin 10 is HIGH and otherwise it read current data and turn on LEDs.  The program compares the current data to the stored (in EEPROM of the ATmega328P) calibration data. There are three stages of displaying: 1) If the value is within certain range, it will turn on the central red LED connected to the digital pin 7; 2) If the value is greater than the range (sens in the program) and less than 2 times of the range (sens), it will turn on the red LED and one green LED at the opposite (in order to simulate the bubble direction) side of the tilt; 3) If the value is greater than 2 times of the range (sens), then only the green LED is turned on.

The calibration values are the neutral value of each axis reading when it is leveled on that axis. For example, the Nunchuck data reading is between 60-70 (these values are different from sensor to sensor) at -g (upside down) on that axis and is over 170 at g (up right). So the neutral (leveled) value of each axis is about 120-130. The calibration begins when the pin 10 goes HIGH by connected to V+ with a small push button switch pressed. One the calibration process begins, in order to give the user some time to put the device down on a flat surface, it waits until the central red LED blinks a few times. The actual calibration is done really quickly and followed by a few quicker blinks.

/*
* Wii_Nunchuck_Level
* Feb-Mar 2012, Jinseok Jeon
* http://jeonlab.wordpress.com
*
* Wii Nunchuck data read:
*  nunchuck_funcs.h from WiiChuckDemo by Tod E. Kurt,
*  http://todbot.com/blog/2008/02/18/wiichuck-wii-nunchuck-adapter-available/
*/

#include <Wire.h>
#include <EEPROM.h>
#include "nunchuck_funcs.h"

byte accl[3]; //accelerometer readings for x, y, z axis
int calPin = 10; //calibration pin
int sens = 1; //sensitivity
int orient;

void setup()
{
  nunchuck_init();
  for (int i=5;i<10;i++) {
    pinMode(i, OUTPUT);
  } //9 left, 8 up, 7 center, 6 down, 5 right
  pinMode(calPin, INPUT);
  delay(100);
}

void loop()
{
  //if the calibration pin is pressed, jump to funcion calibrate()
  if (digitalRead(calPin) == HIGH) calibrate();

  getData();
 
  if (orient == 1 || orient == 2) {
    if (abs(accl[0]-EEPROM.read(0 + orient*10)) <= 2*sens) digitalWrite(7, HIGH);
    if (accl[0]-EEPROM.read(0 + orient*10) > sens) digitalWrite(5, HIGH);
    if (EEPROM.read(0 + orient*10)-accl[0] > sens) digitalWrite(9, HIGH);
  }
  if (orient == 3 || orient == 4) {
    if (abs(accl[1]-EEPROM.read(1 + orient*10)) <= 2*sens) digitalWrite(7, HIGH);
    if (accl[1]-EEPROM.read(1 + orient*10) > sens) digitalWrite(8, HIGH);
    if (EEPROM.read(1 + orient*10)-accl[1] > sens) digitalWrite(6, HIGH);
  }
  if (orient == 5 || orient == 6) {
    if (abs(accl[0]-EEPROM.read(0 + orient*10)) <= 2*sens && abs(accl[1]-EEPROM.read(1 + orient*10)) <= 2*sens) digitalWrite(7, HIGH);
    if (accl[0]-EEPROM.read(0 + orient*10) > sens) digitalWrite(5, HIGH);
    if (EEPROM.read(0 + orient*10)-accl[0] > sens) digitalWrite(9, HIGH);
    if (accl[1]-EEPROM.read(1 + orient*10) > sens) digitalWrite(8, HIGH);
    if (EEPROM.read(1 + orient*10)-accl[1] > sens) digitalWrite(6, HIGH);
  }

  delay(50);
  for (int i=5;i<10;i++) { //turn off all LEDs
    digitalWrite(i, LOW);
  }
}

void getData()
{
  nunchuck_get_data();
  accl[0] = nunchuck_accelx();
  accl[1] = nunchuck_accely();
  accl[2] = nunchuck_accelz();

  orient = orientation(); //get orientation
}

void calibrate()
{
  for (int i=0;i<3;i++) {
    digitalWrite(7, HIGH);
    delay(500);          
    digitalWrite(7, LOW);
    delay(500);
  }

  getData();

  for (int i=0;i<3;i++) {
    EEPROM.write(i + orient*10, accl[i]);
  }

  for (int i=0;i<3;i++) {
    digitalWrite(7, HIGH);
    delay(200);          
    digitalWrite(7, LOW);
    delay(200);
  }
}

int orientation()
{
  if (accl[0] > 125 && accl[0] < 145 && accl[2] > 110 && accl[2] < 140) {
    if (accl[1] > 170) orient = 1; //bottom on floor
    else if (accl[1] < 75) orient = 2; //top on floor
  }
  else if (accl[1] > 110 && accl[1] < 140 && accl[2] > 110 && accl[2] < 140) {
    if (accl[0] > 180) orient = 3; //left on floor
    else if (accl[0] < 90) orient = 4; //right on floor
  }
  else if (accl[1] > 110 && accl[1] < 140 && accl[0] > 125 && accl[0] < 145) {
    if (accl[2] > 170) orient = 5; //back on floor
    else if (accl[2] < 80) orient = 6; //front on floor
  }
  return orient;
}

Step 7: Tests

Finally here are some test results with pictures and a video.

I couldn't upload the video here.  It kept given me HTTP error after 100% uploaded.  So here is the video I uploaded to Youtube instead.