Worklog #2: Handling joystick input on Arduino and LinkIt ONE.

4 minute read

After expanding the LinkIt ONE and Arduino analog inputs, I had to find a proper way to handle user input. The easier and most convenient way was using an analog joystick, so I ordered one off AliExpress.

As I figured it will take way too much time to get here, I decided I’d open up my Xbox 360 controller and use its joysticks in the meanwhile.

Butchered 360 controller. Red = VCC, Violet = GND, Yellow = A0, Orange = A1.

Joysticks are made by joining two analog pots together (one for vertical movement, and one for horizontal) and a tactile button.

After hooking them up to +5V and GND, I saw they output an analog value > than 900 if they are held up or left, and around zero when held down or right.

The first and best way to do this is by using timer interrupts (check this nice instructable for more info). Doing so, the Arduino checks the joystick for changes pretty much always, no matter where it is in the code. However, I am not sure if this can be done on the LinkIt ONE, as there is no documentation at all about timer interrupts.

The Arduino way – using timer interrupts.

Take a look at the following code:

#define NUMBER_OF_MODES 5 //number of modes/options we want to cycle through

enum Directions {NONE = 0, UP, DOWN, RIGHT, LEFT};

boolean first = true;
char buff[100];
int vert = 0, horiz = 0;
Directions direction = NONE;

void intSetup(){
    //inspired by http://www.instructables.com/id/Arduino-Timer-Interrupts/
    noInterrupts();
    TCCR1B = 0; // TCCR1B register to 0
    TCNT1  = 0; // counter value to 0
    OCR1A = 312; // compare match register - 50Hz
    TCCR1B |= (1 << WGM12); // CTC mode
    TCCR1B |= (1 << CS12) | (1 << CS10); // set 1024 prescaler
    TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
    interrupts(); 
}

void setup() {
    Serial.begin(9600);
    pinMode(A0, INPUT);
    pinMode(A1, INPUT);

    intSetup();
}

ISR(TIMER1_COMPA_vect){
    if(analogRead(A0) > 900){  //if joystick is in the up position
        direction = UP;  //set a flag
        TIMSK1=0;  //and disable timer interrupt
    }
    if(analogRead(A0) < 10){
        direction = DOWN;
        TIMSK1=0;
    }
    if(analogRead(A1) > 900){
        direction = LEFT;
        TIMSK1=0;
    }
    if(analogRead(A1) < 10){ 
        direction = RIGHT;
        TIMSK1=0;
    }
}

void loop() {
    //if we need to go somewhere and the pot is back to normal position
    if(direction != NONE && analogRead(A0) <= 550 && analogRead(A0) >= 300 && \
        analogRead(A1) <= 550 && analogRead(A1) >= 300) {
        switch (direction) { //update variables according to direction
            case UP:
                if(vert == NUMBER_OF_MODES) vert = 0;
                else vert++;
                break;
            case DOWN:
                if(vert == 0) vert = NUMBER_OF_MODES;
                else vert--;
                break;
            case LEFT:
                if(horiz == 0) horiz = NUMBER_OF_MODES;
                else horiz--;
                break;
            case RIGHT:
                if(horiz == NUMBER_OF_MODES) horiz = 0;
                else horiz++;
                break;
        }
        first = true;
        direction = NONE;
        TIMSK1 |= (1 << OCIE1A); //re-enable interrupt so we can read another value
    }

    if(first){
        sprintf(buff, "Vertical position: %d, Horizontal position: %d\n", vert, horiz);
        Serial.print(buff);
        first = false;
    }
    
}

It’s pretty easy: it runs the ISR() function 50 times a second (!) which checks the value of the joystick and updates a “direction” variable accordingly. The code in the main loop then updates the vertical and horizontal positions.

The timer interrupt is disabled then re-enabled so that the Arduino updates the direction only if the previous joystick movement is complete. Shouldn’t we disable them, holding the joystick down for half a sec would make the “position” variable skyrocket (the ISR would run way too many times). This is only replaceable with a delay(), which stops everything in your program and that is why this is the better solution.

Another solution is checking the joystick state at the beginning of the loop() function, without using timer interrupts. This is what I’m currently using on the LinkIt ONE.

No interrupts – for both Arduino and LinkIt ONE

Here it is:

#define NUMBER_OF_MODES 5 //number of modes/options we want to cycle through

enum Directions {NONE = 0, UP, DOWN, RIGHT, LEFT};

boolean first = true;
char buff[100];
int vert = 0, horiz = 0;
Directions direction = NONE;

void setup(){
    Serial.begin(9600);
    pinMode(A0, INPUT);
    pinMode(A1, INPUT);
}

void checkJoystick(){
    if(analogRead(A0) > 900){  //if joystick is in the up position
        direction = UP;  //set a flag
    }
    if(analogRead(A0) < 10){
        direction = DOWN;
    }
    if(analogRead(A1) > 900){
        direction = LEFT;
    }
    if(analogRead(A1) < 10){ 
        direction = RIGHT;
    }
}

void updateModes(){
    if(direction != NONE && analogRead(A0) <= 550 && analogRead(A0) >= 300 && analogRead(A1) <= 550 && analogRead(A1) >= 300){ //if we need to go somewhere and the pot is back to normal position
        switch (direction) { //update variables according to direction
            case UP:
                if(vert == NUMBER_OF_MODES) vert = 0;
                else vert++;
                break;
            case DOWN:
                if(vert == 0) vert = NUMBER_OF_MODES;
                else vert--;
                break;
            case LEFT:
                if(horiz == 0) horiz = NUMBER_OF_MODES;
                else horiz--;
                break;
            case RIGHT:
                if(horiz == NUMBER_OF_MODES) horiz = 0;
                else horiz++;
                break;
        }
        first = true;
        direction = NONE;
    }
}


void loop(){

    checkJoystick();
    updateModes();

    if(first){
        sprintf(buff, "Vertical position: %d, Horizontal position: %d\n", vert, horiz);
        Serial.print(buff);
        first = false;
    }   
}</pre>

As you can see, the joystick checking code is put at the beginning of the main loop. This is not a problem is your program is not doing anything, but you may run into some trouble if the Arduino is busy as it may miss your joystick movement.

Button handling (for confirmation and exiting/entering modes!) coming early next week.

Have fun!