The iPod controller that I submitted for the mbed challenge
Dependencies: mbed Motordriver PID
Revision 0:371773dd3dd1, committed 2011-05-04
- Comitter:
- networker
- Date:
- Wed May 04 15:41:13 2011 +0000
- Commit message:
- first publication
Changed in this revision
--- /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, ®, 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, ®, 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