Understanding the demokit.pde Arduino Sketch

Examples are great ways to learn a new language or technology. To demonstrate the functionality of the Android Open Accessory ADK, the kit comes with a demo Arduino Sketch that demonstrates how the device communicates with the Arduino Base Board and Android Demo Shield. Unfortunately I have yet to find any good documentation on the example and how it works so I thought I’d comment on the file for those that may not understand.

The post is in “blog” format with paragraphs shoved into the middle of code. For reference you can find the original demokit.pde file over in this Gist.

demokit.pde

The Arduino language is based on C/C++ so the file starts off with the typical headers you’ll need for the various libraries used in the sketch (servos, LEDs, capacitive touch, USB, etc).

#include <Wire.h>
#include <Servo.h>
#include <Max3421e.h>
#include <Usb.h>

These two are the ones you added to the Arduino application libraries when you set up your system for ADK development.

#include <AndroidAccessory.h>
#include <CapSense.h>

Next, you’ll see several ‘#define’ constants. These indicate which pins are attached to which features on the Android Demo Shield. If you look at the shield, you’ll notice each pin has a corresponding number. For example #define LED3_RED 2 indicates that the third LED’s red component pin is using #2 on the shield.

LED3 Red pin location

Technically you don’t need to do this and you could just use 2 throughout your code but defining a constant makes it much easier to read and understand the code when you use LED3_RED instead of referring to the board to see what pin 2 is.

#define  LED3_RED       2
#define  LED3_GREEN     4
#define  LED3_BLUE      3

#define  LED2_RED       5
#define  LED2_GREEN     7
#define  LED2_BLUE      6

#define  LED1_RED       8
#define  LED1_GREEN     10
#define  LED1_BLUE      9

#define  SERVO1         11
#define  SERVO2         12
#define  SERVO3         13

#define  TOUCH_RECV     14
#define  TOUCH_SEND     15

#define  RELAY1         A0
#define  RELAY2         A1

#define  LIGHT_SENSOR   A2
#define  TEMP_SENSOR    A3

#define  BUTTON1        A6
#define  BUTTON2        A7
#define  BUTTON3        A8

#define  JOY_SWITCH     A9      // pulls line down when pressed
#define  JOY_nINT       A10     // active low interrupt input
#define  JOY_nRESET     A11     // active low reset output

Now it’s time to identify the actual accessory. Read my previous post on “Identifying Your Android USB Accessory “ for more information but you can see here that the demokit.pde file identifies itself as version 1.0 of the Google DemoKit accessory.

AndroidAccessory acc("Google, Inc.",
             "DemoKit",
             "DemoKit Arduino Board",
             "1.0",
             "http://www.android.com",
             "0000000012345678");

Now, all that’s left to do is initialize a few variables, setup the environment and then start the loop that will continuously run to evaluate the IO for the Arduion board.

// Define an array for the servos
Servo servos[3];

// Initiate the touch sensor
// 10M ohm resistor on demo shield
CapSense   touch_robot = CapSense(TOUCH_SEND, TOUCH_RECV);

// Run the initial setup on the board
void setup();

// Loop and check for IO 
void loop();

Initialization

The pinMode() method is used to initialize the input and output pins on the Android Demo Shield. It configures the specified pin to behave either as an INPUT or an OUTPUT. Arduino pins default to INPUT so the mode doesn’t need to be explicitly set but its usually a good idea to do it anyway so that your code is clear and understandable.

For example, the init_buttons function identifies the pins for BUTTON1, BUTTON2, BUTTON3 and JOY_SWITCH as input connections.

void init_buttons()
{

    pinMode(BUTTON1, INPUT);
    pinMode(BUTTON2, INPUT);
    pinMode(BUTTON3, INPUT);
    pinMode(JOY_SWITCH, INPUT);

To identify the state of the buttons, a pin configured as an INPUT can have a HIGH or LOW value. Writing a HIGH value with will enable an internal 20K pullup resistor. Writing LOW will disable the pullup. The pullup resistor acts as a known value when no other input is present (such as when pressing the button). Later you can use digitalRead() to check the value to determine if the button has been pressed or not.

    digitalWrite(BUTTON1, HIGH);
    digitalWrite(BUTTON2, HIGH);
    digitalWrite(BUTTON3, HIGH);
    digitalWrite(JOY_SWITCH, HIGH);
}

Output pins, such as the relays in init_relays() are also configured using the pinMode() function however no default value needs to be written to the relays because it is always told to be either on or off.

// Initializes the relays as outputs
void init_relays()
{
    pinMode(RELAY1, OUTPUT);
    pinMode(RELAY2, OUTPUT);
}

The digitalWrite() method can also take varying values as inputs. For example, you may want to write an integer between 1 and 255 to represent the brightness of an LED. The init_leds() function sets all the LED pins as outputs and presets their output value to 1 (or bright).

// Initialize the LED outputs
void init_leds()
{

    digitalWrite(LED1_RED, 1);
    digitalWrite(LED1_GREEN, 1);
    digitalWrite(LED1_BLUE, 1);

    pinMode(LED1_RED, OUTPUT);
    pinMode(LED1_GREEN, OUTPUT);
    pinMode(LED1_BLUE, OUTPUT);
   
    digitalWrite(LED2_RED, 1);
    digitalWrite(LED2_GREEN, 1);
    digitalWrite(LED2_BLUE, 1);

    pinMode(LED2_RED, OUTPUT);
    pinMode(LED2_GREEN, OUTPUT);
    pinMode(LED2_BLUE, OUTPUT);

    digitalWrite(LED3_RED, 1);
    digitalWrite(LED3_GREEN, 1);
    digitalWrite(LED3_BLUE, 1);

    pinMode(LED3_RED, OUTPUT);
    pinMode(LED3_GREEN, OUTPUT);
    pinMode(LED3_BLUE, OUTPUT);
}

The joystick controls are little tricker so I’m not going to go into it in this post. If you’re interested see the init_joystick() and related functions at the bottom of the demokit.pde file that comes with the kit.

void init_joystick(int threshold);

Setup

Now that all the necessary helper functions are defined we can look at the setup function. The setup() function begins serial communications with the device and then calls all the initialization methods you saw earlier.

// These are a few variables to hold the state of the buttons.
byte b1, b2, b3, b4, c;

// Setup the board
void setup()
{
    // Begin communications.
    Serial.begin(115200);
    Serial.print("\r\nStart");

    init_leds();
    init_relays();
    init_buttons();
    init_joystick( 5 );

Next, setup() calibrates the capacitive touch sensor and sets the servos to their initial positions of 90 degrees.

    // autocalibrate OFF
    touch_robot.set_CS_AutocaL_Millis(0xFFFFFFFF);

    servos[0].attach(SERVO1);
    servos[0].write(90);
    servos[1].attach(SERVO2);
    servos[1].write(90);
    servos[2].attach(SERVO3);
    servos[2].write(90);

Lastly, setup() assigns the current HIGH or LOW value of the buttons to b1, b2, b3 and b4. During the loop execution, these values will be compared to the values in the loop and used to determine if the button’s state has changed since the last loop.

    b1 = digitalRead(BUTTON1);
    b2 = digitalRead(BUTTON2);
    b3 = digitalRead(BUTTON3);
    b4 = digitalRead(JOY_SWITCH);
    c = 0;

Now the setup power’s on the accessory.

    acc.powerOn();
}

Loop

The loop() function is the heart beat of the Arduino Base Board. After creating and executing the setup() function, which is responsible for initializing the initial values, the loop begins it’s magic and loops consecutively, allowing the sketch to analyze and respond accordingly.

The loop in demokit.pde has two main responsibilities. First, if the device is connected, it will read any input and write output to the device as necessary. Second, if no device is connected it will reset all the initial values to await a new device connection.

void loop()
{
    byte err;
    byte idle;
    static byte count = 0;
    byte msg[3];
    long touchcount;

Here it looks for a connection.

    if (acc.isConnected()) {
 
        int len = acc.read(msg, sizeof(msg), 1);
        int i;
        byte b;
        uint16_t val;
        int x, y;
        char c0;

If the length of the input buffer from the attached Android device is greater than zero, the loop process the input command to determine what it should do.

        if (len > 0) {

The message format can be seen in the DemoKit Android App that also comes with the kit. If you take a look at the sendCommand() method in the com.google.android.DemoKit.DemoKitActivity class. You’ll see something like this:

...
buffer[0] = command;
buffer[1] = target;
buffer[2] = (byte) value; 
...

In the case of a LED or Servo command, the command’s value is 2 (DemoKitActivity.LED_SERVO_COMMAND), followed by the target and the value to associate with the target.

For example, you can see in the following code that the red, green and blue pins for each LED are updated, but only when msg[0] is 2 (0x2). The LED pin to update is defined in msg[1] (0x0 thru 0x8 respectively) and the value is set using msg[2] and analogWrite().

In the case of the value for a LED, the lower the value the brighter the light (or the higher the value the more black and darker the light). The DemoKit app on the Android device sends 255 when it wants a full bright light so the value is subtracted from 255 to set the appropriate value for “bright”.

            // Assumes only one command per packet.
            if (msg[0] == 0x2) {
                if (msg[1] == 0x0)
                    analogWrite(LED1_RED, 255 - msg[2]);
                else if (msg[1] == 0x1)
                    analogWrite(LED1_GREEN, 255 - msg[2]);
                else if (msg[1] == 0x2)
                    analogWrite(LED1_BLUE, 255 - msg[2]);
                else if (msg[1] == 0x3)
                    analogWrite(LED2_RED, 255 - msg[2]);
                else if (msg[1] == 0x4)
                    analogWrite(LED2_GREEN, 255 - msg[2]);
                else if (msg[1] == 0x5)
                    analogWrite(LED2_BLUE, 255 - msg[2]);
                else if (msg[1] == 0x6)
                    analogWrite(LED3_RED, 255 - msg[2]);
                else if (msg[1] == 0x7)
                    analogWrite(LED3_GREEN, 255 - msg[2]);
                else if (msg[1] == 0x8)
                    analogWrite(LED3_BLUE, 255 - msg[2]);

The servo output uses the same command and is processed in the same if condition however setting the servo position is a little different. You can read more in the Servo library but the basics here are that the servo’s write() method takes an angle for the servo and the map() method is used to re-map the input value from one range (0-255) to another (0-180).

                else if (msg[1] == 0x10)
                    servos[0].write(map(msg[2], 0, 255, 0, 180));
                else if (msg[1] == 0x11)
                    servos[1].write(map(msg[2], 0, 255, 0, 180));
                else if (msg[1] == 0x12)
                    servos[2].write(map(msg[2], 0, 255, 0, 180));
            
            } else if (msg[0] == 0x3) {

For the two relays on the Android Demo Shield, msg[2] is simply compared for a HIGH or LOW value and the board is updated accordingly.

                if (msg[1] == 0x0)
                    digitalWrite(RELAY1, msg[2] ? HIGH : LOW);
                else if (msg[1] == 0x1)
                    digitalWrite(RELAY2, msg[2] ? HIGH : LOW);
            
            }
            
        }

At this point in the loop, all the input messages have been processed so the loop switches to checking all the output pins and sending any necessary messages to the device. The message type for output is set to 1 and each individual output is checked.

        msg[0] = 0x1;

For example, in the following code the state of BUTTON1 is read into b and compared to b1. Earlier you saw how the read value of the pin associated with BUTTON1 could be either HIGH or LOW. If the value of b differs from the value of b1 (defined in setup() or in the previous run of the loop) then the state of the button has changed and the appropriate message is sent back to the connected Android device. At the same time, b is assigned to b1 to indicate the new state for the next loop.

        b = digitalRead(BUTTON1);
        if (b != b1) {
            msg[1] = 0;
            msg[2] = b ? 0 : 1;
            acc.write(msg, 3);
            b1 = b;
        }

        // Check button 2
        b = digitalRead(BUTTON2);
        if (b != b2) {
            msg[1] = 1;
            msg[2] = b ? 0 : 1;
            acc.write(msg, 3);
            b2 = b;
        }

        // Check button 3
        b = digitalRead(BUTTON3);
        if (b != b3) {
            msg[1] = 2;
            msg[2] = b ? 0 : 1;
            acc.write(msg, 3);
            b3 = b;
        }

        // Check for a press on the joystick button.
        b = digitalRead(JOY_SWITCH);
        if (b != b4) {
            msg[1] = 4;
            msg[2] = b ? 0 : 1;
            acc.write(msg, 3);
            b4 = b;
        }

Once all the digital reads for the buttons have completed, the demokit uses an analogRead() to determine the current values of the temperature sensor, the light sensor, the joystick and the capacitive touch sensor. At first I was confused by the use of a switch statement here until I realized the expense of an analog read. It takes about 100 microseconds to read an analog input, so the maximum reading rate is 10,000 times a second. That’s pretty good but we don’t want to slow things down unnecessarily, especially when environmental variables such as temperature and ambient light are not changing dramatically at a rapid pace. The switch and count++ % 0x10 condition allows the loop to reduce the frequency of analog reads and only sample one analog read per loop.

        switch (count++ % 0x10) {
        case 0:
            val = analogRead(TEMP_SENSOR);
            msg[0] = 0x4;
            msg[1] = val >> 8;
            msg[2] = val & 0xff;
            acc.write(msg, 3);
            break;

        case 0x4:
            val = analogRead(LIGHT_SENSOR);
            msg[0] = 0x5;
            msg[1] = val >> 8;
            msg[2] = val & 0xff;
            acc.write(msg, 3);
            break;

        case 0x8:
            read_joystick(&x, &y);
            msg[0] = 0x6;
            msg[1] = constrain(x, -128, 127);
            msg[2] = constrain(y, -128, 127);
            acc.write(msg, 3);
            break;

        case 0xc:
            touchcount = touch_robot.capSense(5);

            c0 = touchcount > 750;

            if (c0 != c) {
                msg[0] = 0x1;
                msg[1] = 3;
                msg[2] = c0;
                acc.write(msg, 3);
                c = c0;
            }

            break;
        }
    } else {

As mentioned earlier, when a device is not connected (or was disconnected) the loop resets the outputs to the default values.

        analogWrite(LED1_RED, 255);
        analogWrite(LED1_GREEN, 255);
        analogWrite(LED1_BLUE, 255);
        analogWrite(LED2_RED, 255);
        analogWrite(LED2_GREEN, 255);
        analogWrite(LED2_BLUE, 255);
        analogWrite(LED3_RED, 255);
        analogWrite(LED3_GREEN, 255);
        analogWrite(LED3_BLUE, 255);
        servos[0].write(90);
        servos[0].write(90);
        servos[0].write(90);
        digitalWrite(RELAY1, LOW);
        digitalWrite(RELAY2, LOW);
    }

Then the loop ends by delaying for a few milliseconds.

    delay(10);
}

The remainder of the file deals with the IO of the joystick. You can just take it as is and I’ll leave it until another post.

Hopefully this was a little helpful getting you started with the ADK and the demokit example.