The iPod controller that I submitted for the mbed challenge

Dependencies:   mbed Motordriver PID

Files at this revision

API Documentation at this revision

Comitter:
networker
Date:
Wed May 04 15:41:13 2011 +0000
Commit message:
first publication

Changed in this revision

Motordriver.lib Show annotated file Show diff for this revision Revisions of this file
PID.lib Show annotated file Show diff for this revision Revisions of this file
fader.cpp Show annotated file Show diff for this revision Revisions of this file
fader.h Show annotated file Show diff for this revision Revisions of this file
filter/filter.cpp Show annotated file Show diff for this revision Revisions of this file
filter/filter.h Show annotated file Show diff for this revision Revisions of this file
ipod.cpp Show annotated file Show diff for this revision Revisions of this file
ipod.h Show annotated file Show diff for this revision Revisions of this file
ipodcontrol.cpp Show annotated file Show diff for this revision Revisions of this file
ipodcontrol.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
user_interface/MCP23017.cpp Show annotated file Show diff for this revision Revisions of this file
user_interface/MCP23017.h Show annotated file Show diff for this revision Revisions of this file
user_interface/TextLCD23017.cpp Show annotated file Show diff for this revision Revisions of this file
user_interface/TextLCD23017.h Show annotated file Show diff for this revision Revisions of this file
user_interface/async_i2c.cpp Show annotated file Show diff for this revision Revisions of this file
user_interface/async_i2c.h Show annotated file Show diff for this revision Revisions of this file
user_interface/buzzer.h Show annotated file Show diff for this revision Revisions of this file
user_interface/keybrd.cpp Show annotated file Show diff for this revision Revisions of this file
user_interface/keybrd.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Motordriver.lib	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/littlexc/code/Motordriver/#3110b9209d3c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PID.lib	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/WarwickRacing/code/PID/#9fe5d80c3c5e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fader.cpp	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,134 @@
+#include "fader.h"
+#define MUSTSTOP    //some motordrivers will not change direction without a stop
+//#define GETSTAT
+
+int n =0;
+float sumprod = 0.0, sum = 0;;
+
+DigitalOut track(LED2); //debug only
+
+#ifdef USESUPPLY
+AnalogIn Vmotor(p17);
+float getVmotor() {
+    return Vmotor*13.3;    //for 3.3V referemce and 100k,33k divider
+}
+#else
+float getVmotor() {
+    return 6.0;    //assume 6V supply
+}
+#endif
+
+
+servo::servo(PinName p, PinName f, PinName r, PinName a): Motor(p, f, r, 0) {
+    const float Ts = 0.001;//sample period in seconds, 1kHz
+    deadband = 0.001; // +/- 0.5mm, noise???
+    _pwm.period_us(100); //10kHz otherwise very annoying sound
+    fb = new AnalogIn(a);
+    flt = new medianFilter(7);
+    float voltage = getVmotor(); //make the gain dependent on the supply voltage
+    float Kp = 9.0 - 0.5*voltage; //gain=6 @6V
+    pid = new PID(Kp , 0.05, 0.000002, Ts);//work well @6V motor supply
+    pid->setInputLimits(0, 1.0);
+    pid->setOutputLimits(-1.0, 1.0);
+    pid->setBias(0.0);//just to set internal feedforward variable
+    _setPoint = 0.0;
+    pid->setSetPoint(_setPoint);
+    tick.attach(this,&servo::process, Ts);
+}
+
+void servo::process() {
+    update();
+    float p = pos();//(filtered) value of the potentiometer as a value between 0.0 and 1.0
+    pid->setProcessValue(p); //set it as the value to control (Ist-wert)
+    float out = pid->compute(); //compute the motor speed
+#ifdef MUSTSTOP
+    if ((out > 0 && _out < 0) || (out < 0 && _out > 0))
+        _out = 0.0;//stop first
+    else
+        _out = out;
+#else
+    _out = out;
+#endif
+    if (fabs(p - _setPoint) < deadband) {
+        coast(); //near setpoint so disconnect motor to allow manual movement
+        coasting = true;
+    } else {
+        speed(_out);
+        coasting = false;
+    }
+}
+
+fader::fader(PinName p, PinName f, PinName r, PinName a, PinName t): servo(p,f,r,a) {
+    thres = 0.01;
+    thres2 = 0.001;
+    count = 0;
+    command = 0;
+    state = tracking;
+    if (t != NC)
+        touch = new AnalogIn(t);
+    else
+        touch = 0;
+}
+
+void fader::process() { //called by the ticker every 1 ms
+    float p;
+    switch (state) { //make sure that each branch calls either servo::process or servo::update for proper filtering
+        case tracking:
+            servo::process();
+            p = pos();
+            if (isCoasting()) { //servo is near it's setpoint, motor is off
+                state = holding;
+                lastpos = p; //save the position that was reached
+                track = 1; //debug
+            }
+            break;
+        case holding:
+            update();
+            p = pos();
+            if (fabs(lastpos - p) > thres) { //apparently position has changed (manual move)
+                state = moving;
+                printf("moving from %f to %f\n", lastpos, p);
+                lastpos = p;
+                count = 0;
+            }//if not, stay in 'holding' until next 'set' command, do not update lastpos
+            break;
+        case moving:
+            //not tracking but coasting
+            update();
+            p = pos();
+            if (fabs(lastpos - p) < thres2) {
+                count++;
+            } else {
+                count = 0;
+            }
+            if (count > 100) { //apparently movement has stopped, movement less then thres2 for 100ms
+                printf("movement stopped at %f\n", p);
+                servo::set(p); //update the servo setpoint (has no effect because state is not tracking)
+                if (command)
+                    command(p); //invoke the OnMove handler
+                state = holding; //go back to holding state to allow a new fader::set and return to tracking
+            }
+            lastpos = p;
+            break;
+        default: //cannot happen
+            update();
+            p = pos();
+    }
+    if (touch && *touch > thres) { //stub: if input fullfills some condition (touch sense on fader)
+        if (command)
+            command(p);
+    }
+#ifdef GETSTAT
+    sum += p;
+    sumprod += p*p;
+    n++;
+    if (n == 1000) {
+        float mean = sum/n;
+        float var = sumprod/n - mean*mean;
+        printf("n=%d, E=%f, Var = %f, sDev=%f\n", n, mean, var, sqrt(var));
+        n=0;
+        sum=0;
+        sumprod=0;
+    }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fader.h	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,93 @@
+#ifndef FADER_H
+#define FADER_H
+#include "motordriver.h"
+#include "PID.h"
+#include "filter.h"
+
+extern AnalogIn Vmotor;
+extern DigitalOut track;
+
+
+class servo: public Motor {//class to control a DC motor with analog (potentiometer) feedback
+    AnalogIn *fb;
+    PID *pid;
+    Ticker tick;
+    float _setPoint;
+    float _out;
+    float deadband;
+    bool coasting;
+    float med;
+    filter *flt;
+protected:
+    virtual void process();
+    void reset() {
+        pid->reset();
+    }
+    void update() {
+        med = flt->process(*fb);
+    }
+public:
+    servo(PinName p, PinName f, PinName r, PinName a);
+    virtual ~servo() {
+        delete pid;
+        delete fb;
+        delete flt;
+    }
+//setters
+    virtual void set(float v) {
+        _setPoint = v;
+        pid->setSetPoint(v);
+    printf("%f %%\n", v*100);
+    }
+//getters
+    float pos() {
+        //return *fb;
+        return med;
+    }
+    float setPoint() {
+        return _setPoint;
+    }
+    float output() {
+        return _out;
+    }
+    bool isCoasting() {
+        return coasting;
+    }
+};
+
+class fader: public servo {
+    float thres, thres2;
+    int count;
+    float lastpos;
+    AnalogIn *touch;
+    void (*command)(float);
+    enum { holding, tracking, moving} state;
+protected:
+    virtual void process() ;
+public:
+    fader(PinName p, PinName f, PinName r, PinName a, PinName t=NC);
+    virtual ~fader() {
+        if (touch)
+            delete touch;
+    }
+//setters
+    virtual void set(float v) {
+        if (state==moving)
+            return; //cannot accept new value while wiper is being moved
+        if (state==holding)
+            reset(); //clear cummulative error after coasting
+        state = tracking;//this is the only way to make the servo active again
+        servo::set(v);
+//    printf("%f%%\n", v*100);
+        track = 0; //debug
+    }
+    void setOnMove(void(*f)(float)) {
+        command = f;
+    }
+    void setMoveThreshold(float t) {
+        thres = t;
+    }
+//getters
+    //pos is inherited
+};
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/filter/filter.cpp	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,81 @@
+#include <float.h>
+#include "filter.h"
+
+medianFilter::medianFilter(int window): N(window) {
+    big = new bool[N];
+    val = new float[N];
+    big = new bool[N];
+    i = 0;
+    for (int j = 0; j < N; j++) {
+        val[j] = 0;
+        big[j] = j > N/2;
+    }
+    med = 0;
+    median=0;
+}
+
+int medianFilter::findmax() {
+    float m = -FLT_MAX;
+    int n = -1;
+    for (int j = 0; j < N; j++) {
+        if (j == med) continue;
+        if (!big[j]) { //find max
+            if (val[j] > m) {
+                m = val[j];
+                n = j;
+            }
+        }
+    }
+    return n;
+}
+
+int medianFilter::findmin() {
+    float m = FLT_MAX;
+    int n = -1;
+    for (int j = 0; j < N; j++) {
+        if (big[j]) { //find min
+            if (val[j] < m) {
+                m = val[j];
+                n = j;
+            }
+        }
+    }
+    return n;
+}
+
+float medianFilter::process(float in) {
+    //the value at position 'i' is to be replaced by 'in' and the new median is computed
+    //var 'median' refers to the old median
+    //  val[j] <= median <= val[k]
+    //by convention the mediam is considered small
+    val[i] = in;
+    if (i == med) { //the median itself is removed (not the value but the actual sample)
+        if (in <= median) { //the new value is smaller than or equal to the old median and may be the new median
+            med = -1;  //hack to include the median cell in the comparison
+            med = findmax(); //the largest small value is the new median
+        } else { //the new value is larger than the old median and may be the new median
+            big[i] = true; //add the new val to the big set, which is now 1 too large
+            med = findmin();
+            big[med] = false;
+        }
+    } else if (!big[i]) {//old value is removed from small values
+        if (in <= median) {
+            //replace small with small, median not affected
+        } else { //the new value is large
+            big[i] = true;
+            med = findmin();
+            big[med] = false;
+        }
+    } else  { //old value is large
+        if (in <= median) { //but the new value is small
+            big[i] = false;
+            big[med] = true;
+            med = findmax();
+        } else {//new value is also large
+            //replace large with large, median not affected
+        }
+    }
+    if (++i >= N) i = 0;
+    median = val[med];
+    return median;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/filter/filter.h	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,24 @@
+#ifndef FILTER_H
+#define FILTER_H
+
+class filter {
+public:
+    virtual float process(float in) {
+        return in;
+    }
+};
+
+class medianFilter: public filter {
+    int N;
+    float *val;
+    bool *big;
+    int med, i;
+    float median;
+    int findmax();
+    int findmin();
+public:
+    medianFilter(int window = 3); //every window >= 1 is allowed but the behaviour for even window sizes is not well defined
+    virtual float process(float);
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipod.cpp	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,380 @@
+#include "mbed.h"
+#include "ipod.h"
+
+ipod::ipod(PinName tx, PinName rx): led(*new DigitalOut(DEBUGPIN)) {
+    com = new Serial(tx, rx);
+    com->baud(19200);//should work upto 57600
+    rx_ready = false;
+    tx_ready = true;
+    state = 0;
+    tx_buffer[0] = 0xff;
+    tx_buffer[1] = 0x55;
+    com->attach(this, &ipod::handlerx);
+#ifdef TX_IRQ
+    com->attach(this, &ipod::handletx, Serial::TxIrq);
+#endif
+    lastcommand = -1;
+    error = err_success;
+    replies = 0;
+}
+
+void ipod::handlerx() {//Serial port interrupt handler
+    led = 1;
+    unsigned short c = com->getc();
+//   printf("%d: %02X; ", state, chk);
+    switch (state) {
+        case 0:
+            if (c==0xff) state++;
+            break;
+        case 1:
+            if (c==0x55) state++;
+            else state = 0;
+            break;
+        case 2:
+            if (rx_ready) {
+                state = 0;
+                break;//buffer not free, ignore the message
+            }
+            length = c;
+            chk = c;
+            rx_buffer = new char[length];
+            state++;
+            break;
+        case 3:
+            if (c > 4) {
+                state = 0;
+                break;//mode not known or not supported
+            }
+            mode = c;
+            chk += c;
+            state++;
+            break;
+        case 4:
+            command =  c<<8;
+            chk += c;
+            state++;
+            break;
+        case 5:
+            command += c;
+            chk += c;
+            state++;
+            break;
+        default:
+            chk += c;
+            if (state < length+3) {
+                rx_buffer[state-6] = c;
+                state++;
+            } else {
+                if (chk==0) {
+                    rx_ready = true;
+                    replies--;
+                    if (command-1 == lastcommand) {//it is a reply to lastcommand
+                        //printf("reply to %02X\n", lastcommand);
+                        lastcommand = -1;
+                    }
+                    //printf("ready %02X\n", command);
+                } else
+                    printf("checksum error %02x state=%d\n", chk, state);
+                state = 0;
+                break;
+            }
+    }
+    led = 0;
+}
+
+void ipod::handletx() {
+    if (tx_index < tx_size)
+        com->putc(tx_buffer[tx_index++]);
+    else
+        tx_ready = true;
+}
+
+bool ipod::waitForReply() {//busy waits for a reply to the last issued command, all other replies are ignored
+    for (int i = 0; i < 10000000; i++) {
+        if (ready()) {
+            if (lastcommand == -1) {//indicates that a reply to the last issued command has been received
+                parse();//parse the last reply
+                return true;//return to caller for further processing
+            }
+            release();//release the buffer and hence ignore the message
+        }
+    }
+    printf("timeout: last received %02X(%d, %d, %d, \"%s\")\n", command, arg1, arg2, arg3, string);
+    error = err_timeout;
+    return false;
+}
+
+#define BIGENDIAN
+void ipod::copy(char *b, union conv p) {//copy an UINT32 argument bytewise to the buffer
+#ifdef BIGENDIAN
+    for (int i = 0; i < 4; i++)
+        *(b+3-i) = p.asBytes[i];
+#else
+    for (int i = 0; i < 4; i++)
+        *(b+i) = p.asBytes[i];
+#endif
+}
+
+void ipod::SendAirCmd(unsigned cmd, unsigned arg1, unsigned arg2, unsigned arg3) {//send an advanced ipod remote command, unused arguments are optional
+    union conv par1, par2, par3;
+    par1.asInt = arg1;
+    par2.asInt = arg2;
+    par3.asInt = arg3;
+    tx_buffer[3] = 4; //AiR mode
+    tx_buffer[4] = 0;
+    tx_buffer[5] = cmd & 0xff;
+    unsigned expect = 1; //typically expect 1 reply per request
+    switch (cmd) {
+        case 0x12: //get ipod type
+            tx_buffer[2] = 3;
+            //expect 2 bytes return
+            break;
+        case 0x14: //get iPod name
+            tx_buffer[2] = 3;
+            //expect string return
+            break;
+        case 0x16: //main lib
+            tx_buffer[2] = 3;
+            expect = 0;
+            break;
+        case 0x17: //goto item
+            tx_buffer[6] = arg1;
+            tx_buffer[2] = 8;
+            copy(tx_buffer+7, par2);
+            expect = 0;
+            break;
+        case 0x18: //get count
+            tx_buffer[6] = arg1;
+            tx_buffer[2] = 4;
+            //expect integer return
+            break;
+        case 0x1A: //get names
+            tx_buffer[6] = arg1;
+            tx_buffer[2] = 12;
+            copy(tx_buffer+7, par2);
+            copy(tx_buffer+11, par3);
+            //expect many offset,string pairs
+            expect = arg3;
+            break;
+        case 0x1C: //get time/stat
+            tx_buffer[2] = 3;
+            //expect int,int,byte
+            break;
+        case 0x1E: //get position
+            tx_buffer[2] = 3;
+            //expect int
+            break;
+        case 0x20: //get title
+            tx_buffer[2] = 7;
+            copy(tx_buffer+6, par1);
+            //expect string
+            break;
+        case 0x22: //get artist
+            tx_buffer[2] = 7;
+            copy(tx_buffer+6, par1);
+            //expect string
+            break;
+        case 0x24: //get album
+            tx_buffer[2] = 7;
+            copy(tx_buffer+6, par1);
+            //expect string
+            break;
+        case 0x26: //poll mode
+            tx_buffer[6] = arg1;
+            tx_buffer[2] = 4;
+            //expect many byte,int pairs
+            expect = 0; //the number to expect is unknown here, so either stop polling or just insert command anyway
+            break;
+        case 0x28: //run PL
+            tx_buffer[2] = 7;
+            copy(tx_buffer+6, par1);
+            expect = 0;
+            break;
+        case 0x29: //command
+            tx_buffer[6] = arg1;
+            tx_buffer[2] = 4;
+            expect = 0;
+            break;
+        case 0x2C: //get shuffle
+            tx_buffer[2] = 3;
+            //expect byte
+            break;
+        case 0x2E: //set shuffle
+            tx_buffer[6] = arg1;
+            tx_buffer[2] = 4;
+            expect = 0;
+            break;
+        case 0x2F: //get repeat
+            tx_buffer[2] = 3;
+            //expect byte
+            break;
+        case 0x31: //set repeat
+            tx_buffer[6] = arg1;
+            tx_buffer[2] = 4;
+            expect = 0;
+            break;
+        case 0x35: //get nr in PL
+            tx_buffer[2] = 3;
+            //expect int
+            break;
+        case 0x37: //goto song
+            tx_buffer[2] = 7;
+            copy(tx_buffer+6, par1);
+            expect = 0;
+            break;
+        case 0x38: //select
+            tx_buffer[6] = arg1;
+            tx_buffer[2] = 9;
+            copy(tx_buffer+7, par2);
+            tx_buffer[11] = 0;//unknown
+            break;
+        default:
+            return;
+    }
+    tx_size = tx_buffer[2] + 4;
+    tx_buffer[tx_size-1] = 0;
+    for (int i = 2; i < tx_size-1; i++)
+        tx_buffer[tx_size-1] -= tx_buffer[i];//compute checksum
+    tx_index = 1;
+    replies = expect;
+#ifdef TX_IRQ
+    while (!tx_ready) /* wait */;
+    tx_ready = false;
+    com->putc(tx_buffer[0]);//kick-off writing buffer
+#else
+    write();
+#endif
+    lastcommand = cmd;
+    printf("%02X (%d, %d, %d)\n", cmd, arg1, arg2, arg3);
+}
+
+void ipod::guarded_SendAirCmd(unsigned cmd, unsigned arg1, unsigned arg2, unsigned arg3) {//same as SendAirCmd but waits until all previous expected replies have been received
+    for (int i = 0; i<1000000; i++)
+        if (replies==0) {
+            SendAirCmd(cmd, arg1, arg2, arg3);
+            return;
+        }
+    printf("Timeout while waiting for reply to %02X, command %02X cannot be send\n", lastcommand, cmd);
+}
+
+void ipod::SetMode(int m) { //char buf[] = {0xff, 0x55, 0x03, 0x00, 0x01, 0x00, 0x00};
+    tx_buffer[2] = 3; //length
+    tx_buffer[3] = 0; //mode 0, mode switching
+    tx_buffer[4] = 1; //cmd high byte
+    tx_buffer[5] = m; //cmd low byte
+    tx_buffer[6] = 0x100 - 3 - 1 - m;//checksum
+    tx_index = 1;
+    tx_size = 7;
+#ifdef TX_IRQ
+    while (!tx_ready) /* wait */;
+    tx_ready = false;
+    com->putc(tx_buffer[0]);
+#else
+    write();
+#endif
+}
+
+unsigned ipod::copy(char* s) {//return a bytewise argument from the buffer as a UINT32
+    union conv p;
+#ifdef BIGENDIAN
+    for (int i = 0; i < 4; i++)
+        p.asBytes[3-i] = s[i];
+#else
+    for (int i = 0; i < 4; i++)
+        p.asBytes[i] = s[i];
+#endif
+    return p.asInt;
+}
+
+void ipod::parse() {
+    error = err_success;
+    switch (mode) {
+        case 0: //mode 0 reply
+            if (command & 0xFF == 4) {//reply to mode status request
+                printf("mode 0 reply = %04X\n", command);
+            } else
+                printf( "unexpected mode 0 reply\n");
+            break;  //assume s.c_str()[4] == 0
+        case 4://reply to AiR command
+            switch (command) {
+                case 0:
+                    if (rx_buffer[0]==0x04) {
+                        error = err_unknown;
+                        printf("command %04X is not understood\n", *(unsigned short*)&rx_buffer[1]);
+                    }
+                    break;
+                case 1:
+                    error = (errors)rx_buffer[0];
+                    switch (error) {
+                        case err_success:
+                            //printf("command %04X succeeded\n",*(unsigned short*)&rx_buffer[1]);
+                            break;
+                        case err_failure:
+                            printf("command %04X failed\n",*(unsigned short*)&rx_buffer[1]);
+                            break;
+                        case err_limit:
+                            printf("command %04X had wrong parameter(s)\n",*(unsigned short*)&rx_buffer[1]);
+                            break;
+                        case err_answer:
+                            printf("command %04X is an answer\n",*(unsigned short*)&rx_buffer[1]);
+                            break;
+                        default:
+                            printf("unknown error\n");
+                            break;
+                    }
+                    break;
+                    //2 bytes
+                case get_ipod_size+1: //ipod type
+                    break;
+                    //single string
+                case get_ipod_name+1:
+                case get_title+1:
+                case get_artist+1:
+                case get_album+1:
+                    string = rx_buffer;
+                    printf("%04X: %s\n", command, string);
+                    break;
+                    //number+string
+                case get_names+1:
+                    string = rx_buffer + 4;
+                    arg1 = copy(rx_buffer);
+                    printf("%04X: %d %s\n", command, arg1, string);
+                    break;
+                    //number+number+byte
+                case get_time_status+1:
+                    arg1 = copy(rx_buffer);
+                    arg2 = copy(rx_buffer+4);
+                    arg3 = rx_buffer[8];
+                    printf("%04X: %d %d %02X\n", command, arg1, arg2, arg3);
+                    break;
+                    //number
+                case get_count+1:
+                case get_position+1:
+                case get_nr_in_playlist+1:
+                    arg1 = copy(rx_buffer);
+                    printf("%04X: %d\n", command, arg1);
+                    break;
+                    //byte + number
+                case polling+1:
+                    arg1 = rx_buffer[0];
+                    arg2 = copy(rx_buffer+1);
+                    break;
+                    //byte
+                case get_shuffle+1:
+                case get_repeat+1:
+                    arg1 = rx_buffer[0];
+                    printf("%04X: %02X\n", command, arg1);
+                    break;
+                    //10 bytes
+                case select+1: //select
+                    break;
+                default:
+                    printf("unsupported reply");
+                    break;
+            }
+            break;
+        default:
+            printf("unsupported mode\n");
+            break;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipod.h	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,77 @@
+#ifndef IPOD_H
+#define IPOD_H
+
+//#define TX_IRQ
+#define DEBUGPIN    LED2
+
+//enum types { top, playlist, artist, album, genre, song, composer, podcast, endoflist};
+enum playback { play = 1, stop, next, prev, ffwd, frwd, stopf};
+enum aircmd { get_ipod_size = 0x12, get_ipod_name = 0x14, switch_to_main = 0x16, switch_to_item = 0x17, get_count = 0x18, get_names = 0x1a, get_time_status = 0x1c,
+              get_position = 0x1e, get_title = 0x20, get_artist = 0x22, get_album = 0x24, polling = 0x26, play_list = 0x28, command = 0x29,
+              get_shuffle = 0x2c, set_shuffle = 0x2e, get_repeat = 0x2f, set_repeat = 0x31, get_nr_in_playlist = 0x35, jump_to_nr_in_playlist = 0x37, select = 0x38
+            };
+
+class ipod {
+    union conv { int asInt;
+        char asBytes[4];
+    };
+    void copy(char *b, union conv p) ;
+    unsigned copy(char *b) ;
+    Serial *com;
+    void handlerx();
+    void handletx();
+    unsigned char state, length, mode, chk;
+    volatile short command, lastcommand;
+    char *rx_buffer, tx_buffer[16];
+    char tx_size, tx_index;
+    bool rx_ready, tx_ready;
+    enum errors { err_success=0, err_timeout=1, err_failure=2, err_limit=4, err_answer=5, err_unknown}error;
+    char *string; //just a pointer into rx_buffer
+    unsigned arg1, arg2, arg3;
+    volatile unsigned replies;
+    DigitalOut &led;
+protected:
+    void write() {
+        for (int i = 0; i < tx_size; i++)
+            com->putc(tx_buffer[i]);
+    }
+public:
+    ipod(PinName tx, PinName rx);
+    ~ipod() {
+        delete com;
+    }
+//commands
+   void SendAirCmd(unsigned cmd, unsigned arg1=0, unsigned arg2=0, unsigned arg3=0);
+   void guarded_SendAirCmd(unsigned cmd, unsigned arg1=0, unsigned arg2=0, unsigned arg3=0);
+    void SetMode(int);
+//event processing
+    bool ready() {
+        return rx_ready;
+    }
+    void release() {
+        delete[] rx_buffer;
+        rx_ready = false;
+    }
+    void parse();
+    bool waitForReply();
+//getters
+    short cmd() {
+        return command;
+    }
+    const char* text() const {
+        return string;
+    }
+    unsigned Arg1() {
+        return arg1;
+    }
+    unsigned Arg2() {
+        return arg2;
+    }
+    unsigned Arg3() {
+        return arg3;
+    }
+    errors getError() {
+        return error;
+    }
+};
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipodcontrol.cpp	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,282 @@
+#include "mbed.h"
+#include "ipodcontrol.h"
+
+char* strdup(const char *s) {
+    char *r = new char[strlen(s)+1];
+    strcpy(r, s);
+    return r;
+}
+
+/* There are 6 different paths through the ipod menu structure, they all start at the toplevel and end at the song level.
+   The longest path has 5 levels and each type has a variable number of entries, except the toplevel which has always 6 entries (paths).
+   When going down the hierarchy, each type starts at entry 0 (the top of the item list).
+   When going up the hierarchy, each type starts at the entry it was at when going down, this is remembered in array 'last'.
+*/
+//enum types { top, playlist, artist, album, genre, song, composer, podcast};
+const char *ipodControl::toplevel[] = {"Playlist","Artist","Album","Genre","Song","Composer"};//do not change order!
+const types ipodControl::paths[][maxdepth] = {
+    {top,playlist,song},
+    {top,artist,album,song},
+    {top,album,song},
+    {top,genre,artist,album,song},//path with the maximum depth
+    {top,song},
+    {top,composer,album,song}
+};
+
+ipodControl::ipodControl(ipod& p): pod(p)  {
+    mode = nav;
+    path = 0;
+    level = 0;
+    items = sizeof(toplevel)/sizeof(char*);
+    current = 0;
+    OnGetNames = 0;
+    OnTrackChange = 0;
+    OnTime = 0;
+    OnTitle = 0;
+    OnAlbum = 0;
+    OnArtist = 0;
+    OnStatus = 0;
+    OnError = 0;
+    wrap = false;
+    pod.SetMode(4);
+}
+
+bool ipodControl::readName() {
+    name = 0;
+    pod.SendAirCmd(get_ipod_name);
+    if (pod.waitForReply())
+        pod.release();
+    if (pod.getError()) {
+        if (OnError)
+            OnError(pod.getError(), get_ipod_name);
+        return false;
+    }
+    return true;
+}
+
+//the menu structure currently has no <All> items
+void ipodControl::OK(unsigned item) {//when the user presses 'OK' (rotary encoder), we move down the menu hierarchy until we reach the 'song' level, we then switch to playback mode
+    if (mode == nav) {
+        if (paths[path][level] != song) { //present level is not yet 'song'
+            last[level] = item; //push the current position
+            if (level>0) //not at the toplevel
+                pod.SendAirCmd(select, paths[path][level], item); //'select' the current item
+            level++;
+            current = 0;  //start at beginning (no forward history)
+            pod.SendAirCmd(get_count, paths[path][level]); //get the number of subitems
+            pod.waitForReply();
+            items = pod.Arg1();
+            pod.release();
+            if (items>0)
+                pod.SendAirCmd(get_names, paths[path][level],0U,1U);  //display the first subitem
+            printf("new level: %s\n", toplevel[paths[path][level]-1]);         //diag. display the new level category
+        } else { //pressed OK at song level
+            mode = playback;
+            pod.SendAirCmd(select, paths[path][level], item); //'select' the current item
+            pod.SendAirCmd(play_list, item); //execute the current item, start playing
+            Update();
+        }
+    } else //mode>=play
+    { //real ipod will cycle through volume, position(elapsed), cover_art and stars
+    }
+}
+
+void ipodControl::Menu() {//when the user presses 'menu' we move up the menu structure, we enter navigation mode but playback(if active) continues
+    if (mode != nav) {
+        mode = nav;
+        return;
+    }
+    if (level > 0) {
+        level--;
+        if (level > 0) { //still not at top level
+            pod.SendAirCmd(get_count, paths[path][level]);  //get number of items at new level
+            pod.waitForReply();
+            items = pod.Arg1();
+            pod.release();
+            if (items>0) {
+                pod.SendAirCmd(get_names, paths[path][level],last[level],1U);  //display the item at the pushed position, or...
+                current = last[level];//pop the last position
+            } else {
+                current = 0;
+            }
+            printf("new level: %s\n", toplevel[paths[path][level]-1]);         //diag. display the new level category
+        } else { //reached toplevel
+            items = sizeof(toplevel)/sizeof(char*);
+            current = path;
+            printf("new level: Music\n");         //diag. display the new level category
+            pod.SendAirCmd(select, 1U); //'select' all  (entire iPod)
+            //pod.SendAirCmd(switch_to_main); //switch to main library
+        }
+    }
+    //else was already at top => do nothing
+}
+
+void ipodControl::Right() {
+    if (mode == nav) {
+        Next();
+        if (paths[path][level] == top)  //top level
+            path = current;
+        else //get name of item at new position
+            pod.SendAirCmd(get_names, paths[path][level],current,1U);
+    } else
+        pod.SendAirCmd(command, next);//skip fwd
+}
+
+void ipodControl::Left() {
+    if (mode == nav) {
+        Prev();
+        if (paths[path][level] == top) { //top level
+            path = current;
+        } else {//get name of item at new position
+            pod.SendAirCmd(get_names, paths[path][level],current,1U);
+        }
+    } else
+        pod.SendAirCmd(command, prev);//skip back
+}
+
+void ipodControl::MoveTo(float pos) {
+    newpos = pos;
+    switch (mode) {
+        case playback:
+            if (pos < elapsed) { //reverse
+                //newpos = pos;
+                mode = fastr;
+                printf("moving back to %f sec\n", 0.001*pos);
+                pod.SendAirCmd(command, frwd);//assume that polling is active and that the command is accepted
+            } else if (pos > elapsed) { //forward
+                //newpos = pos;
+                mode = fastf;
+                printf("moving fwd to %f sec\n", 0.001*pos);
+                pod.SendAirCmd(command, ffwd);
+            }
+            break;
+        case fastf:
+            //newpos = pos;
+            printf("> moving fwd to %f sec\n", 0.001*pos);
+            break;
+        case fastr:
+            //newpos = pos;
+            printf("< moving back to %f sec\n", 0.001*pos);
+            break;
+        case nav:
+            printf("N");
+            break;
+    }
+}
+
+void ipodControl::poll() {//this is called as part of the main event loop
+    if (pod.ready()) {
+        pod.parse();
+        switch (pod.cmd()-1) {
+            case get_count:
+                items = pod.Arg1();
+                break;
+            case get_names:
+                if (OnGetNames)
+                    OnGetNames(pod.Arg1(), pod.text());
+                break;
+            case get_position:
+                current = pod.Arg1();
+                break;
+            case polling:
+                if (pod.Arg1()==1) {//track change
+                    current = pod.Arg2();
+                    mode = playback;
+                    if (OnTrackChange)
+                        OnTrackChange(current);
+                } else {//elapsed time
+                    elapsed = pod.Arg2();
+                    switch (mode) {
+                        case playback:
+                            if (OnTime)
+                                OnTime(elapsed);
+                            break;
+                        case fastf:
+                            printf(" F ");
+                            if (elapsed >= newpos)
+                                mode = stopfast;
+                            break;
+                        case fastr:
+                            printf(" R ");
+                            if (elapsed <= newpos)
+                                mode = stopfast;
+                            break;
+                        case stopfast:
+                            printf(" S ");
+                            if (elapsed >= newpos)
+                                mode = playback;
+                            break;
+                    }
+                    if (mode == stopfast) {
+                        printf("reached position %f sec\n", 0.001*elapsed);
+                        pod.SendAirCmd(command, stopf);
+                    }
+                }
+                break;
+            case get_ipod_name:
+                if (name) delete[] name;
+                name = strdup(pod.text());
+                break;
+            case get_title:
+                if (OnTitle)
+                    OnTitle(pod.text());
+                break;
+            case get_artist:
+                if (OnArtist)
+                    OnArtist(pod.text());
+                break;
+            case get_album:
+                if (OnAlbum)
+                    OnAlbum(pod.text());
+                break;
+            case get_time_status: //arg1=tracklength in ms, arg2=elapsed time, arg3=status (0=stop, 1=play, 2=pause)
+                tracklength = pod.Arg1();
+                elapsed = pod.Arg2();
+                status = pod.Arg3();
+                if (OnStatus)
+                    OnStatus(tracklength, elapsed, pod.Arg3());
+                break;
+            case 0: //ack/error
+                if (pod.Arg1()>0 && OnError)
+                    OnError(pod.Arg1(), pod.Arg2());
+                break;
+            default:
+                printf("Unknown reply from iPod %04X\n", pod.cmd());
+                break;
+        }
+        unsigned reply = pod.cmd();
+        pod.release();//reset ready flag and delete the receive buffer, meaning that all returned results should be saved or processed
+        updater(reply);
+    }
+}
+
+void ipodControl::updater(unsigned reply) {
+    if (update_state != usIdle)
+        printf("\t\tstate=%d, reply = %02X\n", update_state, reply);
+    switch (update_state) {
+        case usGet_time_status:
+            if (reply==get_time_status+1) {
+                update_state = usGet_title;
+                pod.SendAirCmd(get_title, current);
+                printf("updater: getting title\n");
+            } else if (reply==polling+1) {//only do this when polling is off
+                pod.SendAirCmd(get_time_status); //reissue the same command and stay in the same state
+                printf("updater: getting status (again)\n");
+            }
+            break;
+        case usGet_title:
+            if (reply==get_title+1) {
+                update_state = usGet_artist;
+                pod.SendAirCmd(get_artist, current);
+                printf("updater: getting artist\n");
+            }
+            break;
+        case usGet_artist:
+            if (reply==get_artist+1) {
+                update_state = usIdle;
+                pod.SendAirCmd(polling, 1);
+                printf("updater: going idle\n");
+            }
+            break;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipodcontrol.h	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,64 @@
+#ifndef IPODCONTROL_H
+#define IPODCONTROL_H
+#include "ipod.h"
+
+enum types { top, playlist=1, artist=2, album=3, genre=4, song=5, composer=6, podcast=8};
+enum _mode { nav, playback, fastf, fastr, stopfast};
+
+class ipodControl {
+    static const int maxdepth = 5;
+    static const char *toplevel[];
+    static const types paths[][maxdepth];
+    unsigned path, level;
+    _mode  mode;
+    unsigned items, current;
+    unsigned last[maxdepth];
+    unsigned elapsed, tracklength, newpos;
+    unsigned status;
+    ipod& pod;
+    char *name;
+    bool wrap;
+    void Next() { current++; if (current == items) current = wrap ? 0 : items-1;}
+    void Prev() { if (current == 0) current = wrap ? items-1 : 0; else current--;}
+    enum { usIdle, usGet_time_status, usGet_title, usGet_artist, usStartPoll, usStopPoll} update_state;
+protected:
+    void updater(unsigned);
+public:
+    ipodControl(ipod& p) ;
+//commands
+    void OK() { OK(current);}
+    void OK(unsigned item);
+    void Menu();
+    void Fwd() {pod.SendAirCmd(command, next);}
+    void Rev() {pod.SendAirCmd(command, prev);}
+    void PlayPause() {pod.SendAirCmd(command, play);}
+    void Right();
+    void Left();
+    void MoveTo(float);
+    bool readName();
+    void StartPolling() { pod.SendAirCmd(polling, 1);}
+    void Update(){update_state = usGet_time_status; pod.SendAirCmd(polling, 0); pod.SendAirCmd(get_time_status); printf("Update: getting status\n");}
+//setters
+    void setWrap(bool w=true) { wrap = w;}
+//getters
+    _mode getMode(){ return mode;}
+    const char* getPathStr() const { return toplevel[path];}
+    const char* getTypeStr() const { types t = paths[path][level]; if (t==top) return "Music"; return toplevel[t-1];}
+    types getType() { return paths[path][level];}
+    unsigned getItems() { return items;}
+    unsigned getElapsed() { return elapsed;}
+    unsigned getTrackLength() { return tracklength;}
+    float getPos() { return (float)elapsed/(float)tracklength;}
+//event processing
+    void poll();
+    void (*OnTitle)(const char*);
+    void (*OnAlbum)(const char*);
+    void (*OnArtist)(const char*);
+    void (*OnGetNames)(unsigned, const char*);
+    void (*OnTrackChange)(unsigned);
+    void (*OnTime)(unsigned);
+    void (*OnStatus)(unsigned, unsigned, unsigned);
+    void (*OnError)(unsigned, unsigned);
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,170 @@
+#include "mbed.h"
+#include "fader.h"
+#include "MCP23017.h"
+#include "TextLCD23017.h"
+#include "keybrd.h"
+#include "ipod.h"
+#include "ipodcontrol.h"
+
+fader trackbar(p21,p22,p23,p20);
+I2C iic(p28, p27);
+MCP23017 ui(iic, 0x40);
+TextLCD23017 lcd(ui);
+keybrd kb(ui, p12);//second argument is the interrupt pin, although only used by keyboard it logically belongs to the MCP23017
+ipod pod(p9, p10);//serial pins for the ipod
+ipodControl cntrl(pod);
+
+DigitalOut led(LED1);//for debug only
+
+const float beyondEnd = 2.0; //any number > 1.0
+float  inmark=0, outmark=beyondEnd;
+
+void show(const char* s) { //write to LCD bottom line
+    lcd.locate(0, 1);
+    lcd.printf("%-16s", s);
+}
+
+void showtop(const char* s) { //write to LCD top line
+    lcd.locate(0, 0);
+    lcd.printf("%-16s", s);
+}
+
+void show(unsigned, const char* s) {
+    lcd.locate(0, 1);
+    if (cntrl.getItems()>0)
+        lcd.printf("%-16s", s);
+    else
+        lcd.printf("    <Empty>     ");//placeholder
+}
+
+void time(unsigned t) { //OnTime, iPod polling command
+    float pos = t;
+    unsigned length = cntrl.getTrackLength();
+    if (length == 0)
+        return;//valid length not available (yet)
+    pos /= length;
+    if (pos >= outmark) {
+        cntrl.MoveTo(inmark * cntrl.getTrackLength());
+        trackbar.set(inmark);
+    } else
+        trackbar.set(pos);//when fader is 'holding' or 'tracking moveto new position
+}
+
+void changeTrack(unsigned t) { //OnTrackChange, iPod polling command
+    printf("Track %u\n", t);
+    //the track changed so we need new artist/song/tracklength
+    //must not call guarded_SendAirCmd here because the rx_buffer will not be released before reply arrives
+    //calling a sequence of normal SendAirCmds is also dangerous because they may not be properly queued
+    cntrl.Update(); //sends the first SendAirCmd and starts the 'updater' state-machine to send the rest
+}
+
+void OnMove(float newpos) { //fader move command, called after manual movement of wiper
+    cntrl.MoveTo(newpos * cntrl.getTrackLength()); //new wiper position in ms
+}
+
+void errHandler(unsigned e, unsigned cmd) { //ipodcontrol::OnError
+    if (e==1 && cmd==get_ipod_name)
+        lcd.printf("iPod connected??");
+}
+
+void handleKey(char c) { //handles the keys of the user interface
+    switch (c) {
+        case 1://OK
+            if (cntrl.getMode() == nav)
+                cntrl.OK();
+            else { //mode==play
+                outmark = beyondEnd;//stop repeating marked section
+                inmark = 0;
+            }
+            break;
+        case 2://>>/out
+            if (cntrl.getMode()==nav)
+                cntrl.Fwd();
+            else //'out' command
+                if (cntrl.getPos() > inmark)
+                    outmark = cntrl.getPos();
+            break;
+        case 3://<</in
+            if (cntrl.getMode()==nav)
+                cntrl.Rev();
+            else //'in' command
+                if (cntrl.getPos() < outmark)
+                    inmark = cntrl.getPos();
+            break;
+        case 4://>||
+            cntrl.PlayPause();
+            break;
+        case 5://menu
+            cntrl.Menu();
+            if (cntrl.getType() == top)
+                show("");
+            break;
+        case 6://rec
+        case 7://replay
+        default:
+            break;
+    }
+}
+
+/* On a real iPod, the wheel has multiple functions, depending on the mode:
+   navigation: moves up and down the current list
+   play: changes volume (not implemented here and afaik not possible)
+        but it has submodes
+            fast: moves quickly through a song (here implemented by fader movement)
+            stars: rates the song (not implemented)
+*/
+int main() {
+    int count = 0;
+    iic.frequency(400000);//does not work, stays at 100kHz
+    cntrl.OnGetNames = show;
+    cntrl.OnTitle = show;
+    cntrl.OnArtist = showtop;
+    cntrl.OnAlbum = show;
+    cntrl.OnTime = time;
+    cntrl.OnTrackChange = changeTrack;
+    cntrl.OnError = errHandler;
+    trackbar.setOnMove(OnMove);
+    if (!cntrl.readName()) //calls errHandler if no reply
+        ;
+    wait(1.0);
+    cntrl.StartPolling();
+    lcd.printf("%-16s", cntrl.getPathStr());//display "Playlist" on topline, bottomline empty
+    printf("entering main loop\n");
+    for (;;) {
+        count  = (count+1)%0x20000;
+        if (count==0) led = !led;//just to see if the main loop keeps running
+
+        lcd.locate(0, 0);//col, row
+
+        switch (kb.getevent(true)) {
+            case keybrd::keydown:
+                printf("Key %01d\n", kb.getc());
+                handleKey(kb.getc());
+                goto update;
+            case keybrd::keyup:
+                break;
+            case keybrd::posup://rotary encoder turns right
+                printf("Pos %d\n", kb.getpos());
+                cntrl.Right();
+                outmark = beyondEnd; //stop repeating
+                inmark = 0;
+                goto update;
+            case keybrd::posdown:
+                printf("Pos %d\n", kb.getpos());
+                cntrl.Left();
+                outmark = beyondEnd; //stop repeating
+                inmark = 0;
+update:
+                if (cntrl.getType() == top)
+                    lcd.printf("%-16s", cntrl.getPathStr());//toplevel name from array
+                else
+                    lcd.printf("%-16s", cntrl.getTypeStr());//item name from iPod
+                break;
+            case keybrd::none:
+                break;
+            default:
+                printf("Unknown keyboard event\n");
+        }
+        cntrl.poll();//handle the ipodcontrol events in the event loop
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/e2ac27c8e93e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_interface/MCP23017.cpp	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,179 @@
+/* MCP23017 - drive the Microchip MCP23017 16-bit Port Extender using I2C
+* Copyright (c) 2010 Wim Huiskamp, Romilly Cocking (original version for SPI)
+*
+* Released under the MIT License: http://mbed.org/license/mit
+*
+* version 0.2 Initial Release
+* version 0.3 Cleaned up
+*/
+
+#include "mbed.h"
+#include "MCP23017.h"
+
+DigitalOut Busy(LED4);
+
+/** Create an MCP23017 object connected to the specified I2C object and using the specified deviceAddress
+*
+* @param I2C &i2c the I2C port to connect to
+* @param char deviceAddress the address of the MSC23017
+*/
+MCP23017::MCP23017(I2C &i2c, char deviceAddress) : _i2c(i2c) {
+printf("creating mcp23017\n");
+    _writeOpcode = deviceAddress & 0xFE; // low order bit = 0 for write
+    _readOpcode  = deviceAddress | 0x01; // low order bit = 1 for read
+    busy = false;
+    posted = false;
+    _init();
+    readW(INTFA);
+    readW(GPIOA);
+printf("mcp23017 created\n");
+}
+
+/** Read from specified MCP23017 register
+*
+* @param char address the internal registeraddress of the MSC23017
+* @returns data from register
+*/
+short MCP23017::_read(char address) {//blocks on busy and still waits for completion
+    char data[2];
+    i2c_status s;
+    data[0] = address;
+    do {
+        s = _read(address, data, 1);
+    } while (s == i2c_busy);
+    return (s == i2c_ok) ? data[0] : -2;
+}
+
+MCP23017::i2c_status MCP23017::_read(char reg, char *data, int size) {//returns on busy but still waits for completion
+    if (testbusy()) return i2c_busy;
+    _i2c.write(_writeOpcode, &reg, 1, true); //in a future version of the i2c lib write should return a status like ACK or NACK or busy
+    int result = _i2c.read(_readOpcode, data, size);
+    releasebusy();
+    return result ? i2c_nack : i2c_ok;
+}
+
+int MCP23017::readW(char address) {//blocks on busy and still waits for completion
+    char data[2];
+    i2c_status s;
+    data[0] = address;
+    do {
+        s = _read(address, data, 2);
+    } while (s == i2c_busy);
+    return (s == i2c_ok) ? *(unsigned short*)data : -1;
+}
+
+
+/** Write to specified MCP23017 register
+*
+* @param char address the internal registeraddress of the MSC23017
+*/
+void MCP23017::_write(char address, char byte) {//blocks on busy and still waits for completion
+    i2c_status s;
+    char data[2];
+    data[0] = address;
+    data[1] = byte;
+    do {
+        s = _write(data, 2);
+    } while (s == i2c_busy);
+//    _i2c.write(_writeOpcode, data, 2);    // Write data to selected Register
+    return ; //s; //in the future return the status
+}
+
+MCP23017::i2c_status MCP23017::_write(char *data, int size, bool rpt) {//returns on busy but still waits for completion
+    if (testbusy()) return i2c_busy;
+    int result = 0;
+    _i2c.write(_writeOpcode, data, size, rpt);    // Write data to selected Register
+    releasebusy();
+    return result ? i2c_nack : i2c_ok;
+}
+
+/** Init MCP23017
+*
+* @param
+* @returns
+*/
+void MCP23017::_init() {
+    _write(IOCON, (IOCON_BYTE_MODE | IOCON_ODR )); // Open drain interrupt, operations toggle between A and B registers
+
+}
+
+/** Set I/O direction of specified MCP23017 Port
+*
+* @param Port Port address (Port_A or Port_B)
+* @param char direction pin direction (0 = output, 1 = input)
+*/
+void MCP23017::direction(Port port, char direction) {
+    _write(port + IODIRA, direction);
+}
+
+/** Set Pull-Up Resistors on specified MCP23017 Port
+*
+* @param Port Port address (Port_A or Port_B)
+* @param char offOrOn per pin (0 = off, 1 = on)
+*/
+void MCP23017::configurePullUps(Port port, char offOrOn) {
+    _write(port + GPPUA, offOrOn);
+}
+
+void MCP23017::interruptEnable(Port port, char interruptsEnabledMask) {
+    _write(port + GPINTENA, interruptsEnabledMask);
+}
+
+void MCP23017::mirrorInterrupts(bool mirror) {
+    char iocon = _read(IOCON);
+    if (mirror) {
+        iocon = iocon | INTERRUPT_MIRROR_BIT;
+    } else {
+        iocon = iocon & ~INTERRUPT_MIRROR_BIT;
+    }
+    _write(IOCON, iocon);
+
+}
+
+void  MCP23017::interruptPolarity(Polarity polarity) {
+    char iocon = _read(IOCON);
+    if (polarity == ACTIVE_LOW) {
+        iocon = iocon & ~INTERRUPT_POLARITY_BIT;
+    } else {
+        iocon = iocon | INTERRUPT_POLARITY_BIT;
+    }
+    _write(IOCON, iocon);
+}
+
+void MCP23017::defaultValue(Port port, char valuesToCompare) {
+    _write(port + DEFVALA, valuesToCompare);
+}
+
+void MCP23017::interruptControl(Port port, char interruptControlBits) {
+    _write(port + INTCONA, interruptControlBits);
+}
+
+/** Write to specified MCP23017 Port
+*
+* @param Port Port address (Port_A or Port_B)
+* @param char byte data to write
+*/
+void MCP23017::write(Port port, char byte) {
+    _write(port + OLATA, byte);
+}
+
+/** Read from specified MCP23017 Port
+*
+* @param Port Port address (Port_A or Port_B)
+* @returns data from Port
+*/
+char MCP23017::read(Port port) {
+    return _read(port + GPIOA);
+}
+
+void MCP23017::write(Port port, const char *buffer, int len, bool rpt) {
+    len++;
+    char *data = new char[len];
+    data[0] = port+OLATA;
+    i2c_status s;
+    for (int k=1; k< len; k++) data[k] = buffer[k-1];
+    do {
+      s = _write(data, len, rpt);//toggle A and B, start with port
+    } while (s == i2c_busy);
+    delete[] data;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_interface/MCP23017.h	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,160 @@
+/* MCP23017 - drive the Microchip MCP23017 16-bit Port Extender using I2C
+* Copyright (c) 2010 Wim Huiskamp, Romilly Cocking (original version for SPI)
+*
+* Released under the MIT License: http://mbed.org/license/mit
+*
+* version 0.2 Initial Release
+* version 0.3 Cleaned up
+*/
+#include "mbed.h"
+
+#ifndef  MCP23017_H
+#define  MCP23017_H
+
+// All register addresses assume IOCON.BANK = 0 (POR default)
+#define IODIRA   0x00
+#define IODIRB   0x01
+#define GPINTENA 0x04
+#define GPINTENB 0x05
+#define DEFVALA  0x06
+#define DEFVALB  0x07
+#define INTCONA  0x08
+#define INTCONB  0x09
+#define IOCON    0x0A
+//#define IOCON    0x0B
+#define GPPUA    0x0C
+#define GPPUB    0x0D
+#define INTFA    0x0E
+#define INTFB    0x0F
+#define INTCAPA  0x10
+#define INTCAPB  0x11
+#define GPIOA    0x12
+#define GPIOB    0x13
+#define OLATA    0x14
+#define OLATB    0x15
+
+// Control settings
+#define IOCON_BANK      0x80 // Banked registers for Port A and B
+#define IOCON_BYTE_MODE 0x20 // Disables sequential operation, Address Ptr does not increment
+//   If Disabled and Bank = 0, operations toggle between Port A and B registers
+#define IOCON_HAEN      0x08 // Hardware address enable
+#define IOCON_ODR       0x04 // Open drain, also disables int polarity!
+#define INTERRUPT_POLARITY_BIT 0x02
+#define INTERRUPT_MIRROR_BIT   0x40
+
+#define PORT_DIR_OUT   0x00
+#define PORT_DIR_IN    0xFF
+
+enum Polarity { ACTIVE_LOW , ACTIVE_HIGH };
+enum Port { PORT_A, PORT_B };
+
+extern DigitalOut Busy;
+
+class MCP23017 {
+    FunctionPointer fp;
+    bool posted;
+public:
+    /** Create an MCP23017 object connected to the specified I2C object and using the specified deviceAddress
+    *
+    * @param I2C &i2c the I2C port to connect to
+    * @param char deviceAddress the address of the MSC23017
+    */
+    MCP23017(I2C &i2c, char deviceAddress);
+    MCP23017(const MCP23017& m):_i2c(m._i2c) {
+        printf("copy constructor\n");
+    }
+
+    /** Set I/O direction of specified MCP23017 Port
+    *
+    * @param Port Port address (Port_A or Port_B)
+    * @param char direction pin direction (0 = output, 1 = input)
+    */
+    void direction(Port port, char direction);
+
+    /** Set Pull-Up Resistors on specified MCP23017 Port
+    *
+    * @param Port Port address (Port_A or Port_B)
+    * @param char offOrOn per pin (0 = off, 1 = on)
+    */
+    void configurePullUps(Port port, char offOrOn);
+
+    void interruptEnable(Port port, char interruptsEnabledMask);
+    void interruptPolarity(Polarity polarity);
+    void mirrorInterrupts(bool mirror);
+    void defaultValue(Port port, char valuesToCompare);
+    void interruptControl(Port port, char interruptControlBits);
+
+    /** Read from specified MCP23017 Port
+    *
+    * @param Port Port address (Port_A or Port_B)
+    * @returns data from Port
+    */
+    char read(Port port);
+    int readW(char address);
+    /** Write to specified MCP23017 Port
+    *
+    * @param Port Port address (Port_A or Port_B)
+    * @param char byte data to write
+    */
+    void write(Port port, char byte);
+    void write(Port port, const char *buffer, int len, bool rpt=false);
+    enum i2c_status { i2c_ok, i2c_busy, i2c_nack, i2c_pending};
+    i2c_status _read(char reg, char *data, int size);
+    i2c_status _write(char *data, int size, bool rpt = false);//first element of data is the register address
+    bool testbusy() {
+        __disable_irq();
+        if (busy) {
+            __enable_irq();
+            return true;
+        }
+        busy = true;
+        Busy = 1;
+        __enable_irq();
+        return false;
+    }
+    bool isbusy() {
+        return busy;
+    }
+    void releasebusy() {
+        busy = false;
+        Busy = 0;
+        if (posted) {
+            posted = false;
+            fp.call();
+        }
+    }
+    template<typename T>
+    void post(T *object, void (T::*member)()) {
+        if (posted)
+            return;
+        fp.attach(object, member);
+        posted = true;
+    }
+protected:
+    I2C &_i2c;
+    char _readOpcode;
+    char _writeOpcode;
+    bool busy;
+
+    /** Init MCP23017
+    *
+    * @param
+    * @returns
+    */
+    void _init();
+
+    /** Write to specified MCP23017 register
+    *
+    * @param char address the internal registeraddress of the MSC23017
+    */
+    void _write(char address, char byte);
+
+    /** Read from specified MCP23017 register
+    *
+    * @param char address the internal registeraddress of the MSC23017
+    * @returns data from register
+    */
+    short _read(char address);
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_interface/TextLCD23017.cpp	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,235 @@
+/* mbed TextLCD23017 Library, for a 4-bit LCD based on HD44780
+ * Copyright (c) 2007-2010, sford, http://mbed.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdarg.h>
+#include "TextLCD23017.h"
+#include "mbed.h"
+
+/**************************************************************
+Port A: databus 8-bit bidir, also used as input for keys (A2-A7)
+IntA wired to mbed pin??
+PortB: B0 out: keys out, set low to read keys on PortA
+       B1 in:  rot.enc. push button act.L
+       B2 out: buzzer
+       B3 in:  rot.enc. A
+       B4 in:  rot.enc. B
+       B5 out: LCD E (CS\)
+       B6 out: LCD R\W (WR\)
+       B7 out: LCD RS (A0)
+**************************************************************/
+TextLCD23017::TextLCD23017(MCP23017& intf, LCDType type) : _intf(intf), _type(type) {
+    ::printf("creating lcd\n");
+    wait(0.015);        // Wait 15ms to ensure powered up of LCD
+//init the MCP23017
+    intf.direction(PORT_A, PORT_DIR_IN); //input
+    intf.direction(PORT_B, INPUTS);// rot enc input
+    intf.configurePullUps(PORT_A, PORT_DIR_IN);
+    intf.configurePullUps(PORT_B, INPUTS);
+    intf.write(PORT_B, 0x00); //enable keys
+    //intf.interruptPolarity(ACTIVE_LOW);
+    intf.mirrorInterrupts(true);
+    
+//initialisation according to HD44780 datasheet
+    // send "Display Settings" 2 times for 8 bit interface
+    writeByte(0x30);
+    wait(0.0041);  // this command takes 4.1ms, so wait for it
+    writeByte(0x30);
+
+
+    writeByte(0x38);     // 8-bit mode, 2 lines 8x5 font
+    //wait(0.000040f);    // most instructions take 40us
+    writeByte(0x08);  //display off, cursor off
+    writeByte(0x01);  //clear display
+    writeByte(0x06);  //increment, no shift
+//from here on busy can be tested
+
+    writeCommand(0x0F); //display on, cursor on, blink on
+//    cls();
+::printf("lcd created\n");
+}
+
+int TextLCD23017::printf (char * format, ...) {
+    char buffer[40];
+    va_list args;
+    va_start (args, format);
+    int rv=vsprintf (buffer,format, args);
+//    ::printf("printing:'%s'\n", buffer);
+    writeString (buffer);
+    va_end (args);
+    return rv;
+}
+
+//busy not used, i2c is slow enough
+bool TextLCD23017::busy() {//assume PORT_A is input, PORT_A intr disabled, takes minimum 10bytes ~1ms
+//setup and hold of RS-E are not met!
+    _intf.write(PORT_B, RW|E|KEYS); //read R=1, E=1, disable keys
+    _ar = _intf.read(PORT_A);
+    _intf.write(PORT_B, KEYS); //read R=0, E=0
+    return (_ar & 0x80) ;//return with keys disabled
+}
+
+void TextLCD23017::character(int column, int row, int c) {
+    int a = address(column, row);
+    writeCommand(a);
+    writeData(c);
+}
+
+void TextLCD23017::cls() {
+    writeCommand(0x01); // cls, and set cursor to 0
+    wait(0.00164f);     // This command takes 1.64 ms
+    locate(0, 0);
+}
+
+void TextLCD23017::locate(int column, int row) {
+    _column = column;
+    _row = row;
+}
+
+int TextLCD23017::_putc(int value) {
+    if (value == '\n') {
+        _column = 0;
+        _row++;
+        if (_row >= rows()) {
+            _row = 0;
+        }
+    } else {
+        character(_column, _row, value);
+        _column++;
+        if (_column >= columns()) {
+            _column = 0;
+            _row++;
+            if (_row >= rows()) {
+                _row = 0;
+            }
+        }
+    }
+    return value;
+}
+
+int TextLCD23017::_getc() {
+    return -1;
+}
+
+void TextLCD23017::writeByte(int value) {//5 transactions of 3 bytes ~150bits ~1.5ms
+    _intf.direction(PORT_A, PORT_DIR_OUT); //set to output
+    _intf.write(PORT_A, value);//put value on data bus
+    _intf.write(PORT_B, E|KEYS);//write instruction register, E=1
+    _intf.write(PORT_B, KEYS);//E returns to 0
+    _intf.direction(PORT_A, PORT_DIR_IN); //set to input
+}
+
+void TextLCD23017::writeCommand(int command) {//3 transactions 3 bytes ~0.9ms + busy + writeByte = 3.4ms
+//    while (busy()) /*wait*/ ;
+    writeByte(command);
+    _intf.write(PORT_B, 0);//enable keys
+}
+
+void TextLCD23017::writeData(int data) {
+//    while (busy()) /*wait*/ ;
+    _intf.direction(PORT_A, PORT_DIR_OUT); //set to output
+    _intf.write(PORT_A, data);//put data on data bus
+    _intf.write(PORT_B, RS|KEYS);//setup RS
+    _intf.write(PORT_B, E|RS|KEYS);//write data register, E=1, RS=1
+    _intf.write(PORT_B, RS|KEYS);//E returns to 0
+    _intf.direction(PORT_A, PORT_DIR_IN); //set to input
+    _intf.write(PORT_B, 0);//enable keys
+}
+
+void TextLCD23017::writeString(char *s) {//convert a string into LCD commands
+    char buffer[240];//size must be > 4*strlen(s) + 7
+    char *p = strtok(s,"\n");
+    _intf.direction(PORT_A, PORT_DIR_OUT); //set to output
+    //assume RS=0, E=0, R/W = 0
+    while (p) {
+//    ::printf("section:'%s'\n", p);
+        int i = 0;
+        if (p != s) {//don't do this the first time (not a newline)
+            _column = 0;
+            if (++_row >= rows()) _row = 0;
+        }
+        buffer[i++] = E|KEYS;//B, clock RS and RW
+        buffer[i++] = address(_column, _row);//A, this data is used
+        buffer[i++] = KEYS;//B, clock data
+        buffer[i++] = address(_column, _row);//A, dummy
+        buffer[i++] = RS|KEYS;//B, setup RS
+        for (int j = 0; j < strlen(p) && i<sizeof(buffer)-4; j++) {
+            buffer[i++] = p[j];//A, dummy
+            buffer[i++] = E|RS|KEYS;//B, clock RS and RW
+            buffer[i++] = p[j];//A, this data is used
+            buffer[i++] = RS|KEYS;//B, clock data
+            if (++_column >= columns()) {
+                _column = 0;
+                if (++_row >= rows())  _row = 0;
+            }
+        }//RS=1, E=0, RW=0
+        p = strtok(0, "\n");
+        buffer[i++] = 0;//A, dummy
+        buffer[i++] = (p!=0) ? KEYS : 0; //B, release RS
+        _intf.write(PORT_B, buffer, i, p != 0);//start with B, send all substrings as a single transaction
+    }
+    _intf.direction(PORT_A, PORT_DIR_IN); //set to input
+}
+
+int TextLCD23017::address(int column, int row) {
+    switch (_type) {
+        case LCD20x4:
+            switch (row) {
+                case 0:
+                    return 0x80 + column;
+                case 1:
+                    return 0xc0 + column;
+                case 2:
+                    return 0x94 + column;
+                case 3:
+                    return 0xd4 + column;
+            }
+        case LCD16x2B:
+            return 0x80 + (row * 40) + column;
+        case LCD16x2:
+        case LCD20x2:
+        default:
+            return 0x80 + (row * 0x40) + column;
+    }
+}
+
+int TextLCD23017::columns() {
+    switch (_type) {
+        case LCD20x4:
+        case LCD20x2:
+            return 20;
+        case LCD16x2:
+        case LCD16x2B:
+        default:
+            return 16;
+    }
+}
+
+int TextLCD23017::rows() {
+    switch (_type) {
+        case LCD20x4:
+            return 4;
+        case LCD16x2:
+        case LCD16x2B:
+        case LCD20x2:
+        default:
+            return 2;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_interface/TextLCD23017.h	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,129 @@
+/* mbed TextLCD Library, for a 4-bit LCD based on HD44780
+ * Copyright (c) 2007-2010, sford, http://mbed.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef MBED_TEXTLCD_H
+#define MBED_TEXTLCD_H
+
+#include "mbed.h"
+#include "MCP23017.h"
+
+/** A TextLCD interface for driving 4-bit HD44780-based LCDs
+ *
+ * Currently supports 16x2, 20x2 and 20x4 panels
+ *
+ * @code
+ * #include "mbed.h"
+ * #include "TextLCD.h"
+ * 
+ * TextLCD lcd(p10, p12, p15, p16, p29, p30); // rs, e, d4-d7
+ * 
+ * int main() {
+ *     lcd.printf("Hello World!\n");
+ * }
+ * @endcode
+ */
+class TextLCD23017 : public Stream {
+public:
+
+    /** LCD panel format */
+    enum LCDType {
+        LCD16x2     /**< 16x2 LCD panel (default) */
+        , LCD16x2B  /**< 16x2 LCD panel alternate addressing */
+        , LCD20x2   /**< 20x2 LCD panel */
+        , LCD20x4   /**< 20x4 LCD panel */
+    };
+
+    /** Create a TextLCD interface
+     *
+     * @param rs    Instruction/data control line
+     * @param e     Enable line (clock)
+     * @param d4-d7 Data lines for using as a 4-bit interface
+     * @param type  Sets the panel size/addressing mode (default = LCD16x2)
+     */
+    TextLCD23017(MCP23017& intf, LCDType type = LCD16x2);
+
+#if DOXYGEN_ONLY
+    /** Write a character to the LCD
+     *
+     * @param c The character to write to the display
+     */
+    int putc(int c);
+
+    /** Write a formated string to the LCD
+     *
+     * @param format A printf-style format string, followed by the
+     *               variables to use in formating the string.
+     */
+    int printf(const char* format, ...);
+#endif
+int printf (char * format, ...);
+
+
+
+
+    bool busy();
+    
+    /** Locate to a screen column and row
+     *
+     * @param column  The horizontal position from the left, indexed from 0
+     * @param row     The vertical position from the top, indexed from 0
+     */
+    void locate(int column, int row);
+
+    /** Clear the screen and locate to 0,0 */
+    void cls();
+
+    int rows();
+    int columns();
+
+protected:
+
+    // Stream implementation functions
+    virtual int _putc(int value);
+    virtual int _getc();
+
+    int address(int column, int row);
+    void character(int column, int row, int c);
+    void writeByte(int value);
+    void writeCommand(int command);
+    void writeData(int data);
+    void writeString(char*);
+
+    MCP23017& _intf;
+    LCDType _type;
+
+    int _column;
+    int _row;
+    char _ar;
+};
+
+#define E   (1<<5)
+#define RS  (1<<7)
+#define RW  (1<<6)
+#define KEYS    (1<<0)
+#define BUZZ    (1<<2)
+#define PUSH    (1<<1)
+#define ROTA    (1<<3)
+#define ROTB    (1<<4)
+#define INPUTS  (PUSH|ROTA|ROTB)
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_interface/async_i2c.cpp	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,71 @@
+#include "mbed.h"
+#if 0
+#include "async_i2c.h"
+
+i2c_status async_i2c::write(char address, char *data, int size, bool stop){
+    if (testbusy()) return i2c_busy;
+    int result = 0;
+    I2C::write(address & 0xFE, data, size, !stop);    // Write data to selected Register
+    releasebusy(stop);
+    if (_we) _we(result ? i2c_nack : i2c_ok, data, stop);
+    return i2c_ok;
+}
+
+i2c_status async_i2c::read(char address, char *data, int size, bool stop){
+    if (testbusy()) return i2c_busy;
+    int result = I2C::read(address | 1, data, size, !stop);
+    releasebusy();
+    if (_re) _re(result ? i2c_nack : i2c_ok, data, stop);
+    return i2c_ok;
+}
+
+void async_i2c::process(i2c_buffer *b){
+  if (b->_adr & 1) //read
+    I2C::read(b->_adr, b->_data, b->_size, _next==0);
+  else
+    I2C::write(b->_adr, b->_data, b->_size, _next==0);
+}
+
+i2c_buffer* async_i2c::write(char address, char *data, int size, bool copy, i2c_buffer *b){
+    if (testbusy()) return 0;
+    i2c_buffer *buf = new i2c_buffer(this, address & 0xFE, data, size, copy, b);
+    int result = 0;
+    //I2C::write(address & 0xFE, data, size, b==0);    // Write data to selected Register
+    process(buf);
+    //with still blocking io, the transmission is complete, in the future this piece should be in the callback
+    buf->notify();
+    return buf;
+}
+
+i2c_buffer* async_i2c::read(char address, char *data, int size, bool copy, i2c_buffer *b){
+    if (testbusy()) return 0;
+    i2c_buffer *buf = new i2c_buffer(this, address | 0x01, data, size, copy, b);
+    process(buf);
+    //with still blocking io, the transmission is complete, in the future this piece should be in the callback
+    buf->notify();
+    return buf;
+}
+
+i2c_buffer* async_i2c::read(char address, char reg, char *data, int size, bool copy){
+    if (testbusy()) return 0;
+    i2c_buffer *recv = new i2c_buffer(this, address | 0x01, data, size, copy);
+    i2c_buffer *send = new i2c_buffer(this, address & 0xFE, &reg, sizeof(reg), true, recv);
+    int result = 0;
+    process(send);
+    //with still blocking io, the transmission is complete, in the future this piece should be in the callback
+    send->notify();
+    return recv;
+}
+#endif
+/*
+void write_done(i2c_status s, char *, bool) { 
+if (s == i2c_ok) {
+  _stat = read();
+}
+}
+
+i2c_status async_i2c::write_read(char address, char *wdata, int wsize, char *rdata, int rsize){
+    write_end(write_done);
+    return write(address, wdata, wsize, false);    // Write data to selected Register
+}
+*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_interface/async_i2c.h	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,101 @@
+#ifndef ASYNC_I2C_H
+#define ASYNC_I2C_H
+
+enum i2c_status { i2c_ok, i2c_busy, i2c_nack, i2c_pending};
+class async_i2c;
+
+class i2c_buffer {
+    async_i2c* _intf;
+    char _adr;
+    char *_data;
+    int _size;
+    i2c_buffer *_next;
+    bool _copy;
+    bool (*notification)(i2c_buffer*);
+    i2c_status stat;
+public:
+    i2c_buffer(async_i2c* intf, char adr, char* data, int size, bool copy=false, i2c_buffer *next=0): _intf(intf), _adr(adr), _data(data), _size(size), _copy(copy), _next(next) {
+        if (copy) {
+            _data = new char[size];
+            memcpy(_data, data, size);
+        }
+        stat = i2c_pending;
+        notification = 0;
+    }
+    ~i2c_buffer() {
+        if (_copy) delete[] _data;
+        _data = 0;
+        if (_next) delete _next;
+    }
+    void set_notification(bool (*fun)(i2c_buffer*)) {
+        if (stat==i2c_pending) notification = fun;
+        else fun(this);
+    }
+    //notify is the callback when the transfer has finished
+    bool notify() {
+        bool result = true;
+        i2c_buffer *buf = _next;
+        if (notification)
+            result = notification(this);
+        if (_intf && _next){
+            _intf->process(_next);
+            }
+        else
+            _intf->releasybusy();
+        return result;
+    }
+    friend async_i2c;
+};
+
+class async_i2c: public I2C { //for now based on the sync i2c and hence not async, in the future should be bare metal and completely async
+public:
+    typedef void trans_end(i2c_status stat, char *data, bool stop);
+private:
+    bool busy, _stop;
+    trans_end *_we, *_re;
+    i2c_status _stat;
+    void process(i2c_buffer*);
+public:
+    async_i2c(PinName sda, PinName scl): I2C(sda, scl) {
+        busy = false;
+        _stop = true;
+        _we = 0;
+        _re = 0;
+        _stat = i2c_ok;
+    }
+    void frequency(int rate) {
+        I2C::frequency(rate);
+    }
+    i2c_status write(char address, char *data, int size, bool stop=true);
+    i2c_status read(char address, char *data, int size, bool stop=true);
+    i2c_buffer* write(char address, char *data, int size, bool copy=false, i2c_buffer *b=0);
+    i2c_buffer* read(char address, char *data, int size, bool copy=false, i2c_buffer *b=0);
+    i2c_buffer* read(char address, char reg, char *data, int size, bool copy=false);
+    void write_end(trans_end *cb) {
+        _we = cb;
+    }
+    void read_end(trans_end *cb) {
+        _re = cb;
+    }
+    bool isbusy() {
+        return busy;
+    }
+
+protected:
+    bool testbusy() {
+        __disable_irq();
+        if (busy && _stop) {
+            __enable_irq();
+            return true;
+        }
+        busy = true;
+        __enable_irq();
+        return false;
+    }
+    void releasebusy(bool stop=true) {
+        _stop = stop;
+        if (stop) busy = false;
+    }
+};
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_interface/buzzer.h	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,29 @@
+#ifndef BUZZER_H
+#define BUZZER_H
+#include "MCP23017.h"
+
+class buzzer {
+    MCP23017& _intf;
+    Timeout t;
+    void stop() {
+        char tmp = _intf.read(PORT_B);
+        tmp &= ~BUZZ;
+        _intf.write(PORT_B, tmp);
+    }
+public:
+    buzzer(MCP23017& intf): _intf(intf) {
+        intf.direction(PORT_B, INPUTS);// rot enc input
+        intf.configurePullUps(PORT_B, INPUTS);
+    }
+    ~buzzer() {
+        t.detach();
+        stop();
+    }
+    void buzz(int ms) {
+        char tmp = _intf.read(PORT_B);
+        tmp |= BUZZ;
+        _intf.write(PORT_B, tmp);
+        t.attach_us(this, &buzzer::stop, ms*1000);
+    }
+};
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_interface/keybrd.cpp	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,94 @@
+#include "mbed.h"
+#include "keybrd.h"
+#include <limits.h>
+
+#define KEYS    (0xFC)
+#define PUSH    (1<<1)
+#define ROTA    (1<<3)
+#define ROTB    (1<<4)
+#define INPUTS  (PUSH|ROTA|ROTB)
+
+keybrd::keybrd(MCP23017& intf, PinName p): _eventhandler(0), _intf(intf), _keys(0), _lastkey(0), _pos(0), _oldpos(0), _state(0)  {
+printf("creating keyboard\n");
+   _min = INT_MIN;
+   _max = INT_MAX;
+//inirq = new DigitalOut(LED3);
+//*inirq = 0;
+    intr = new InterruptIn(p);
+    intr->mode(PullUp);
+    intr->fall(this, &keybrd::handler);
+    intf.interruptControl(PORT_A, 0); //interrupt on pin change from previous value
+    intf.interruptControl(PORT_B, 0); //interrupt on pin change from previous value
+    intf.interruptEnable(PORT_A, KEYS);//interrupt on key-press
+    intf.interruptEnable(PORT_B, INPUTS);//interrupt on change of rotary encoder
+//printf("keyboard created\n");
+}
+
+unsigned short keybrd::get() {
+    _intf.readW(INTCAPA);
+    raw = ~_intf.readW(GPIOA);
+    raw &= (INPUTS<<8) | KEYS;
+    return raw;
+}
+
+void keybrd::handler() {
+//there can be a PORT_A interrupt, meaning a change in the state of one of the 6 buttons
+//or a PORT_B interrupt, meaning a change in the rotary encoder/button
+//PORT_A, interrupt on pin_change, 3 registers: change, snapshot, actual
+//read actual on both ports and compare to previous
+
+//*inirq = 1;
+    if (_intf.isbusy()) {//the i2c interrupt is busy, so we postpone the read until it has finished
+      _intf.post(this, &keybrd::handler);
+//*inirq = 0;
+      return;
+    }
+    unsigned short present = get();//read the relevant pins
+    char keys = (((present>>8) & PUSH) | present);
+    char released = _keys & ~keys;//bitmap of keys just released
+    char pressed = keys & ~_keys;//bitmap of keys just pressed
+    if (keys==0)
+      _lastkey = 0;
+    for (int i = 1; i < 8; i++) {
+        if (released & (1<<i))
+            handleEvent(keyup, i);
+        if (pressed & (1<<i)) {
+            _lastkey = i;
+            handleEvent(keydown, i);
+        }
+    }
+//printf("!%04X %02X %02X %d\n", present, pressed, released, _lastkey);
+    _keys = keys;
+    
+    present >>= 8;
+    present &= ROTA|ROTB;
+    switch (_state) {
+        case 0:
+            if (present & ROTA) _pos--;
+            else if (present & ROTB) _pos++;
+            break;
+        case ROTA:
+            if (present & ROTB) _pos--;
+            else if (!(present & ROTA)) _pos++;
+            break;
+        case ROTB:
+            if (!(present & ROTB)) _pos--;
+            else if (present & ROTA) _pos++;
+            break;
+        default://ROTA|ROTB
+            if (!(present & ROTA)) _pos--;
+            else if (!(present & ROTB)) _pos++;
+            break;
+    }
+    if (_pos>_max) _pos = wrap ? _min : _max;
+    else if (_pos<_min) _pos = wrap ? _max : _min;
+    _state = present & (ROTA|ROTB);
+    
+    if (_state == 0)
+        if (_pos > _oldpos)
+            handleEvent(posup, _pos - _oldpos);
+        else if (_pos < _oldpos)
+            handleEvent(posdown, _oldpos - _pos);
+    _oldpos = _pos;
+ //*inirq = 0;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/user_interface/keybrd.h	Wed May 04 15:41:13 2011 +0000
@@ -0,0 +1,37 @@
+#ifndef HEYBRD_H
+#define KEYBRD_H
+#include "mbed.h"
+#include "MCP23017.h"
+
+class keybrd{
+public:
+  enum event { none, keydown, keyup, posdown, posup };
+  typedef void (*eventhandler)(event ev, char key);
+private:
+  eventhandler _eventhandler;
+ // DigitalOut *inirq;  //for test only  
+protected:
+  MCP23017& _intf;
+  InterruptIn *intr; //should be moved to MCP23017
+  virtual void handler();
+  char _keys;
+  char _lastkey;
+  event _lastevent;
+  int _pos, _oldpos, _min, _max;
+  char _state;
+  bool wrap;
+  void handleEvent(event ev, char key) { _lastevent = ev; if (_eventhandler) _eventhandler(ev, key); }  
+public:
+  keybrd(MCP23017& intf, PinName p);
+  ~keybrd() { intr->fall(0); delete intr;}
+  void attach(eventhandler eh) { _eventhandler = eh;}
+  char getc() { return _lastkey;}
+  int getpos() { return _pos>>2; }
+  void setpos(int p) { _pos = p<<2;}
+  void setposrange(int min, int max, bool w = false) { _min = min<<2; if (_pos<_min) _pos = _min; _max = max<<2; if (_pos>_max) _pos = _max; wrap = w;}
+  event getevent(bool clear = false) { event e = _lastevent; if (clear) _lastevent = none; return e;}
+  unsigned short get();
+  unsigned short raw;//debug
+};
+
+#endif
\ No newline at end of file