Use Accelerometer and Joystick to mimic a mouse. This is for a class project. It is done for educational purpose. It is not practical to real world use.

Dependencies:   C12832_lcd MMA7660 USBDevice mbed

accelestick.cpp

Committer:
thlu
Date:
2014-03-28
Revision:
2:3d012df30652
Parent:
1:03d0f8a4a2d7

File content as of revision 2:3d012df30652:

// Name: T.H. Lu
// Date: 03/27/2014
// Description:
// This program is not for practical use. It is my first mbed C code,
//   and is released only because my instructor mandates it.
// The Accelestick uses the accelerometer and joystick on the mbed to emulate
//   a mouse. It is somewhat awkward to use, which is why it is only intended
//   for educational purpose.
// The accelerometer provides the mouse movement, and the joystick acts as
//   the mouse buttons. A real-time debug mode prints messages onto the USB
//   serial interface.
//  Some features are:
//     1. left joystick emulates left mouse button (with double click support)
//        right joystick emulates right mouse button, center position emulates
//        middle mouse button
//     2. up and down joystick positions emulate mouse scroll
//     3. push and hold left joystick for 1 second to initiate left mouse button
//        press and hold feature. LED1 will lit up. Push left joystick again to
//        release
//     4. press joystick center position down twice to initiate accelestick
//          calibration. This will reduce cursor movement at rest position. LED3
//          will flash rapidly and then remain lit when done
//     5. push right joystick twice to enable debug mode printing. Debug messages
//          are written to USB serial (COM port). LED2 will lit when debug is on
//  LED4 will blink every second if accelestick is initialized properly
//

#include "accelestick.h"

//--- Global Variables declaration ---//

C12832_LCD lcd;         // for debugging
MMA7660 mma(p28, p27);  // I2C Accelerometer
USBMouseKeyboard mouse; // USB mouse
DigitalOut leds[] = {LED1, LED2, LED3, LED4};

DigitalIn jd = p12; // joystick down
DigitalIn jl = p13; // joystick left
DigitalIn jc = p14; // joystick center
DigitalIn ju = p15; // joystick up
DigitalIn jr = p16; // joystick right
BusIn joyb(p12, p13, p14, p15, p16); // joystick in vector form


Max_min_t peaks;
Mouse_state_t mouse_info;
G_int_t offset;       // calibration offset value

int mouse_scroll = 0;  // mouse scroll value
bool calib_on = 0;     // calibrate mouse
bool debug_on = 0;     // print debug message

// two joystick active timers: one for long hold detection, one for double click detection
uint8_t joystick_timers_active[2] = {JS_NONE,JS_NONE};
uint8_t debug_sel;

G_float_t mma_g;         // g values from accelerometer

Ticker tk_led_alive;     // Ticker for flashing LED as program alive beacon
Ticker tk_print_debug;   // Ticker for printing debug information
Timer tm_joystick_dc;    // Timer for joystick double-click detection
Timer tm_joystick_lock;  // Timer for locking mouse button position

RawSerial debug_rs(USBTX, USBRX); // Raw serial is non-blocking, and is used for debugging

// End of Global Variable Declaration


inline void reset_tm_joystick_dc()
{
    joystick_timers_active[0] = JS_NONE;
    tm_joystick_dc.stop();
    tm_joystick_dc.reset();
}

inline void reset_tm_joystick_lock()
{
    joystick_timers_active[1] = JS_NONE;
    tm_joystick_lock.stop();
    tm_joystick_lock.reset();
}

// This function checks for joystick button presses which emulate a mouse's
//   left button, right button, middle button, double click, and scroll
//   features. Double click and press and hold detections are done using a timer
void get_joystick_input()
{
    static uint8_t prev_js = 0;
    static bool debug_sel_en = 0;
    static uint8_t mouse_lock = JS_NONE;

    // clear mouse buttons by default
    mouse_info.button = 0;
    mouse_info.scroll = 0;
    mouse_info.dc = 0;

    if (debug_on && debug_sel_en && (joyb != JS_NONE)) {
        debug_sel_en = 0;
        debug_sel = joyb;
        leds[LED_DEBUG] = 1;
    }

    if (debug_on && (debug_sel == JS_NONE) && (joyb == JS_NONE)) {
        debug_sel_en = 1;
    }

    switch(joyb) { // 1 = down, 2 = left, 4 = center, 8 = up, 16 = right
        case  JS_LEFT:
            // 1 click = left button, 2 clicks = double click
            if ((prev_js != JS_LEFT) && (joystick_timers_active[0] == JS_LEFT)) { // double click left to lock
                mouse_info.dc = 1;
                reset_tm_joystick_dc();
            } else if (!joystick_timers_active[0]) {
                joystick_timers_active[0] = JS_LEFT;
                tm_joystick_dc.start();
            }
            mouse_info.button = MOUSE_LEFT;
            break;
        case  JS_CENTER: // center
            if ((prev_js != JS_CENTER) && (joystick_timers_active[0] == JS_CENTER)) { // double click center to calibrate
                calib_on = 1;
                reset_tm_joystick_dc();
            } else if (!joystick_timers_active[0]) {
                joystick_timers_active[0] = JS_CENTER;
                tm_joystick_dc.start();

            }
            mouse_info.button = MOUSE_MIDDLE;
            break;
        case JS_RIGHT:
            if ((prev_js != JS_RIGHT) && (joystick_timers_active[0] == JS_RIGHT)) { // double click left to turn debug on/off
                debug_on = !debug_on;
                reset_tm_joystick_dc();
                lcd.cls();
                lcd.locate(0,0);
                if (debug_on) {
                    lcd.printf("Debug print is ON\n");
                    debug_sel = JS_NONE;
                    debug_sel_en = 0;
                } else {
                    leds[LED_DEBUG] = 0;
                }
            } else if (!joystick_timers_active[0]) {
                joystick_timers_active[0] = JS_RIGHT;
                tm_joystick_dc.start();
            }
            mouse_info.button = MOUSE_RIGHT;
            break;
        case JS_DOWN:  // down
            reset_tm_joystick_dc();
            break;
        case JS_UP:  // up
            reset_tm_joystick_dc();
            break;
    }

    // Update mouse scroll value
    switch (joyb) {
        case JS_DOWN:  // down
            mouse_scroll++;
            break;
        case JS_UP:  // up
            mouse_scroll--;
            break;
        default:
            mouse_scroll = 0;
    }
    mouse_info.scroll = mouse_scroll;

// timeouit for double-click detectino
    if (tm_joystick_dc.read_ms() > 1000) {
        reset_tm_joystick_dc();
    }

// only support mouse lock feature for left button
    if ((prev_js != joyb) && (joyb == JS_LEFT)) { // click JS_LEFT to unlock
        if (mouse_lock == JS_LEFT) {
            mouse_lock = JS_NONE;
        } else if (joystick_timers_active[1] == JS_NONE) {
            joystick_timers_active[1] = JS_LEFT;
            tm_joystick_lock.start();
        }
    }

    if (joyb != JS_LEFT) {
        reset_tm_joystick_lock();
        // hold JS_LEFT for 1.2 sec to lock
    } else if ((joystick_timers_active[1] == JS_LEFT) && tm_joystick_lock.read_ms() > 1000) {
        mouse_lock = JS_LEFT;
    }

    prev_js = joyb;

    if (mouse_lock == JS_LEFT) {
        mouse_info.button = MOUSE_LEFT;
    }
    leds[LED_LOCK] = (mouse_lock == JS_LEFT);
}


void sample_mma()   // Accelerometer value ranges from -1.5 to 1.5
{
    mma_g.x = mma.x();
    mma_g.y = mma.y();
    mma_g.z = mma.z();
    G_int_t current;

    if (mma_g.x > peaks.max_x) {
        peaks.max_x = mma_g.x;
    } else if (mma_g.x < peaks.min_x) {
        peaks.min_x = mma_g.x;
    }
    if (mma_g.y > peaks.max_y) {
        peaks.max_y = mma_g.y;
    } else if (mma_g.y < peaks.min_y) {
        peaks.min_y = mma_g.y;
    }
    if (mma_g.z > peaks.max_z) {
        peaks.max_z = mma_g.z;
    } else if (mma_g.z < peaks.min_z) {
        peaks.min_z = mma_g.z;
    }

    current = conv_g2int(mma_g);
    if (calib_on) {
        calib_mma(current);
    }
    mouse_info.x = filter_noise(current.x - offset.x);
    mouse_info.y = filter_noise(current.y - offset.y);
    mouse_info.z = filter_noise(current.z - offset.z);
}

void update_mouse()
{
    // x-direction is reversed on PC screen
    mouse.move(-mouse_info.x, mouse_info.y);
    mouse.press(mouse_info.button);
    mouse.scroll(mouse_info.scroll);

    if (mouse_info.dc) {
        mouse.doubleClick();
    }
}

// Used for debugging
void print_debug_msg()
{
    if (debug_on) {
        switch (debug_sel) {
            case JS_LEFT:
                debug_rs.printf("Mx = %1.2f, mx = %1.2f ", peaks.max_x, peaks.min_x);
                debug_rs.printf("My = %1.2f, my = %1.2f ", peaks.max_y, peaks.min_y);
                debug_rs.printf("Mz = %1.2f, mz = %1.2f\r\n", peaks.max_z, peaks.min_z);
                break;
            case JS_CENTER:
                debug_rs.printf("x=%1.2f y=%1.2f z=%1.2f\r\n", mma_g.x, mma_g.y, mma_g.z);
                debug_rs.printf("  x=%0d, y=%0d, z=%0d, button=%0d, scroll=%0d, dc=%0d\r\n",
                                mouse_info.x, mouse_info.y, mouse_info.z, mouse_info.button,
                                mouse_info.scroll, mouse_info.dc);
                break;
            case JS_RIGHT:
                debug_rs.printf("offset.x=%0d, y=%0d, z=%0d\r\n", offset.x, offset.y, offset.z);
                break;
            case JS_UP:
                debug_rs.printf("scroll = %0d\r\n", mouse_scroll);
                break;
        }
    }
}

// Calibrate the accelerometer to work on non-level surface. This function is blocking.
void calib_mma(const G_int_t current)
{
    static uint8_t ctr = 0;
    static G_int_t prev[CALIB_SMPLS]; // average array
    Ticker tk_led_calib;

    int temp;

    tk_led_calib.attach(&flash_led2, 0.05);

    if (debug_on) {
        if (ctr == 0) {
            lcd.cls();
            lcd.locate(0,0);
            lcd.printf("Calibrating mma");
            wait(3);
        } else {
            lcd.printf(".");
        }
    }

    prev[ctr++] = current;

    if (ctr == CALIB_SMPLS) {
        temp = 0;
        for (uint8_t i=0; i<CALIB_SMPLS; i++) {
            debug_rs.printf("CALIB[%0d]: x=%0d, y=%0d, z=%0d\r\n", i, prev[i].x, prev[i].y, prev[i].z);
            temp += prev[i].x;
        }
        offset.x = temp/CALIB_SMPLS; // average

        temp = 0;
        for (uint8_t i=0; i<CALIB_SMPLS; i++) {
            temp += prev[i].y;
        }
        offset.y = temp/CALIB_SMPLS;

        temp = 0;
        for (uint8_t i=0; i<CALIB_SMPLS; i++) {
            temp += prev[i].z;
        }
        offset.z = temp/CALIB_SMPLS;

        ctr = 0;
        calib_on = 0;    // calibration done

        if (debug_on) {
            lcd.cls();
            lcd.locate(0,0);
            lcd.printf("Calibration Completed!\n");
            wait(1);
            lcd.cls();
        }

        tk_led_calib.detach();
        leds[LED_CALIB] = 1;
    }
}

//
int16_t filter_noise (int16_t const num)
{
    if (abs(num) < NOISE_FLOOR) {
        return 0;
    } else if (num < 0) {
        return num + NOISE_FLOOR;
    } else {
        return num - NOISE_FLOOR;
    }
}

// convert mma signed g float value into signed int value
G_int_t conv_g2int (G_float_t mma_g)
{
    G_int_t temp;

    temp.x = (mma_g.x * SCALE_X);
    temp.y = (mma_g.y * SCALE_Y);
    temp.z = (mma_g.z * SCALE_Z);
    return temp;
}


// Ticker cannot attach to functions with parameters
void flash_led2()
{
    leds[LED_CALIB] = !leds[LED_CALIB];
}
void flash_led3()
{
    leds[LED_ALIVE] = !leds[LED_ALIVE];
}

int8_t init_accelestick()
{
    lcd.cls();
    reset_tm_joystick_dc();
    reset_tm_joystick_lock();

    offset.x = 0;
    offset.y = 0;
    offset.z = 1.0*SCALE_Z;

    peaks.max_x=ACCEL_MIN, peaks.max_y=ACCEL_MIN, peaks.max_z=ACCEL_MIN;
    peaks.min_x=ACCEL_MAX, peaks.min_y=ACCEL_MAX, peaks.min_z=ACCEL_MAX;

    if (!mma.testConnection()) {
        lcd.printf("Error in MMA init\n");
        return -1;
    }
    tk_led_alive.attach(&flash_led3, 1.0);
    tk_print_debug.attach(&print_debug_msg, 1.0);
    return 0;

}

void run_accelestick()
{
    get_joystick_input();
    sample_mma();
    update_mouse();
}