Software to drive a monitor unit for a closed circuit rebreather using 3 electrogalvanic oxygen sensor cells run through an amplifier (lm324) . Uses a separate ds1307 clock IC to get timestamp values for logged data.

Dependencies:   DS1307 TextOLED_custom mbed

The main electornics is housed in another pod mounted on the back of the unit. I'm using an mbed lpc11u24 to drive everything which comes with a flash drive for data logging built in. It has an external ds1307 clock chip added and a very cheapo lm324 quad op-amp to amplify the o2 sensor signals from the 10s of mV range by 30x so that ppo2=0.21 corresponds to about 0.3V. I still have to do some ADC averaging with this amplifier and do have to calibrate out the individual offsets of the chip but this works ok now that I've worked out which amp is on which adc...

Rebmon_main.cpp

Committer:
pegcjs
Date:
2013-01-16
Revision:
9:cd3599adfff6
Parent:
8:f45e654b47d0

File content as of revision 9:cd3599adfff6:

//lpc1124lcddemo
// driver for mbed based ppo2 monitoring system for closed circuit rebreather
// reading 3 electrogalvanic oxygen sensors and one pressure sensor
#include "ds1307.h"
#include "mbed.h"
#include "TextOLED.h"

//TODO - MAKE A VERSION THAT DRIVES THE HUD, CHECKS THE 5V SUPPLY AND COMPENSATES THE READINGS IF ITS VARYING (LINEARLY)
// AND PREVENT A HANG IF THE DS1307 FAILS TO RESPOND.

#define DRATIO 0.6420066 // ratio of voltage at pin20 to voltage actually generated by the sensor

//hud LINES
DigitalOut AB(p7); //pins AB for data bits
DigitalOut CP(p5); // clock to shift reg
//DigitalOut MR(p8); // reset to shift reg (low for clear)#
DigitalOut btest(p6); // pin to  drive lastblue led

// offsets for lm324 amp in terms of reading values on adc
#define coff1 -0.013375
#define coff2 -0.00936
#define coff3 -0.0212136



Serial pc(USBTX, USBRX); // tx, rx  for debug and usb pc comunications


//pin assignments and declarations
// LCD display
TextLCD g_lcd(p26, p25, p24, p23, p22, p21);  // RS, E, DB4, DB5, DB6, DB7
//backlight
DigitalOut backlight(p29);

//onboard leds
DigitalOut led1(LED1);
DigitalOut led2(LED2);

// warning leds
DigitalOut red(p34);
DigitalOut green(p33);
DigitalOut blue(p30);


// switches and buttons - these are pulled up by resistors so are active low
DigitalIn CAL(p36);
DigitalIn SW1(p35); // reed switch in display unit
DigitalIn SW2(p10); // reed switch in dispaly unit - NONE FUNCIONAL IN CURRENT HEAD - SWITCH FAILED DURING POTTING
//DigitalIn MODE(p11);// a switchn on the mbed pcb to select between SCR and CCR modes for the LEDs NOT USED ANYMORE

// log data storage
LocalFileSystem local("local");

// adc inputs for sensors
AnalogIn PRESin(p20);
AnalogIn EG1(p19);
AnalogIn EG2(p18);
AnalogIn EG3(p16); 
AnalogIn Vbatt(p17); // battery voltage divided down by 3
AnalogIn V5V(p15); // sense the '5V' output from the max1724 unit - divided down by 2.  Nominally 2.5V===0.757575757' in 3.3V ADC



// realtime clock
DS1307 my1307(p28,p27); // start DS1307 class and give it pins for connections of the DS1307 device

// variables for realtime clock
int sec = 0;
int min = 0;
int hours = 0;
int day = 0;
int date = 0;
int month = 0;
int year = 0;
int seconds=0; // general number of seconds since 2000 etc timestamp variable

int scrubtime=0,scrubold=0; // these are expressed in minutes
int divetime=0;

int flash=0; // variable used top control flashing icons
int state=0; // IMPORTANT - VARIABLE THAT DRIVES HNTE STATE MACHINE STATE=0 = STARTUP, STATE=1=SURFACE  STATE=2= DIVING
float lowsetpoint=0.7,highsetpoint=1.2,switchdepth=10; // variables to determine HUD led states 
//switchdepth is centre of switch region 1m deep if switchdepth=10 then will go to high as descebnd 
//through 10.5 and go back to low when ascending through 9.5
int setpoint=0; // 0=low 1 = high

// variables for the eg cells and pressure sensor eg1calamd eg2cal ar reading when the sensor is in 0.21bar O2 and
//dcal is the reading whe the pressure sensor is at the surface
float eg1cal=0.09,eg2cal=0.09,eg3cal=0.09,pcal=0.1136;
// NB these are updated from /local/cal.dat so values not so important.... eventually

float depth=0,ppo1=0,ppo2=0,ppo3=0  ,Vb=0,pressure=0; // depth, 1st o2 sensor second o2 sensor battery voltage,,Pressure
float fo1=0,fo2=0,fo3=0,mod=55; //%f values,mod

FILE *lp; // file pointer for log file

bool nostop=1, deco=0; // variables to define state for deco



//HUD codes
// make a HUD clock pulse
int clk()
{
    wait_us(1);
    CP=0;
    wait_us(1);
    CP=1;
    return(0);
}

// write 8 bits to the HUD shift register
int HUD_write(char d)
{
    int i=0;
    for(i=7; i>=0; i--) {
        AB=d & (1 << i);
        AB=!AB;
        clk();
    }
    return(0);
}

// make all HUD leds white - useful for warnings etc
int HUD_white()
{
    // set all white;
    HUD_write(255);
    btest=0;

    return(0);
}
// clear the HUD - make al black
int HUD_clr()
{
    HUD_write(0);
    btest=1;
    return(0);
}



// code to detect leap years
int LeapYear(int year) {
    int leap=0;

    if (year % 400==0) leap=1;
    else if (year %100 ==0) leap=0;
    else if (year % 4 ==0) leap=1;
    else leap=0;
    return(leap);
}


//===== sub to get time from ds1307 and create the 'seconds' which is a version of timestamp....
int getseconds() {
    int leap=0,dayofyear=0,timestamp=0;
    int y=0,byear=0;
    int days[12]={0,31,59,90,120,151,181,212,243,273,304,334};
    my1307.gettime( &sec, &min, &hours, &day, &date, &month, &year);
    //simple timestamp = # seconds since midnight jan 1st 2000 if all months were 30 days.
    //int secondst=year*365*24*60*60+month*30*24*60*60+day*24*60*60+hours*60*60+min*60+sec;
    //simple timestamp = # seconds since midnight jan 1st 2000 if all months were 30 days....
    // ie wrong but simpler than the real thing


    // sort out ds1307 definiteion of year
    year=year+2000;
    leap=LeapYear(year);

    // now decide dayofyear
    dayofyear=days[month-1]+date-1;
    if (leap==1 && month >2) dayofyear++; // deal with extra february day in leap year

    // now find number of days since 1970
    for (y=1970; y<year; y++) {
        if (LeapYear(y) == 1) {
            byear += 366*24*60*60;
        } else {
            byear += 365*24*60*60;
        }
    }

    // finally get the seconds right and construct timestamp in seconds since beginning of 1970
    timestamp=(byear)+dayofyear*24*3600+hours*3600+min*60+sec;

    //DEBUG====================================
    // printf("secondst =  %d\t timestamp = %d\t%.2d : %.2d : %d - %.2d:%.2d:%.2d\r",secondst,timestamp,date,month,year,hours,min,sec);

    return(timestamp);

}


void set_custom_char() {
    char cgchar[64]={
        6,9,9,9,9,9,9,15, // battery  symbol                    0 address 64 = 0x40
        28,20,20,20,20,20,29,0, //  0. symbol for ppo2          1 address 72 = 0x48
        8,24,8,8,8,8,29,0, // 1. symbol for ppo2                2 address 80 =0x50
        6,15,15,15,15,15,15,15, // unused                       3 address 88 = 0x58
        31,19,21,21,21,21,19,31,  // unused                     4 address 96 = 0x60
        6,6,6,6,6,0,0,6,             // top char Vmessg         5 address 104 =0x68 - used for Vmessage
        31,17,23,17,29,17,31,0, // bottom char Vmessg           6 address 112 =0x70 -used for Vmessg
        2,6,2,2,2,2,23 // for dec point in depth                7 address 120 =0x78
    };
    int i=0;
// do stuff here to set cstom chars
    g_lcd.writeCommand(0x40); // set start address for CGRAM
    for (i=0; i<64; i++) {
        g_lcd.writeData(cgchar[i]);
    }

}

// stash cal values on local drive
void store() {
    int timestamp=0;
    timestamp=getseconds();
    wait(0.1);
    FILE *fp=fopen("/local/CAL.dat","w");
    fprintf(fp,"%e\n%e\n%e\n%e\n%d\n%d\n",eg1cal,eg2cal,eg3cal,pcal,scrubtime,timestamp);

    fclose(fp); //NB file system locked on write so must make sure we close files in case want to reprogram etc...
    wait(0.1);
}


// subroutine to calibreate o2 sesnors and store ca data in /local/CAL.dat
void calibrate() {
    int count=1;
    float s1=0,s2=0,s3=0,pres=0;
    // average 20 readings for noise reduction
    g_lcd.cls();
    for (count=20; count>0; count--) {
        s1=s1+EG1;
        s2=s2+EG2;
        s3=s3+EG3;
        pres=pres+PRESin;
        g_lcd.locate(0,0);
        g_lcd.printf("CAL 21%% %.2d %1.2f",count,pres/(20-count+1));
       
        g_lcd.locate(0,1);
        g_lcd.printf("%1.2f: %1.2f: %1.2f",s1/(20-count+1),s2/(20-count+1),s3/(20-count+1));
        wait(1);
    }
    //average
    s1=s1/20-coff1;
    s2=s2/20-coff2;
    s3=s3/20-coff3;
    // set calibration variables
    eg1cal=s1;
    eg2cal=s2;
    eg3cal=s3;
    pcal=pres/20/DRATIO; // surface pressure output voltage from sensor
    scrubtime=0; // reset the scrubber timer to zero.
    scrubold=0; // set stored scrub time to zero too.
    // write cal data NB overwites previous
    /*  FILE *fp=fopen("/local/CAL.dat","w");
      fprintf(fp,"%e\n%e\n%e\n%d",eg1cal,eg2cal,pcal,scrubtime);
      fclose(fp); //NB file system locked on write so must make sure we close files in case want to reprogram etc...*/
    store();
}

// sub to test if a variable is an even number
int iseven(int g) {
    int test=0;
    if (g%2 ==0) test=1;
    return(test);
}


void status() {
   /* if (state==0) {
        g_lcd.character(7,0,33); // warning icon until 1 min up
        g_lcd.character(8,0,83); // surface icon - letter S
    } else {
        g_lcd.character(7,0,32);
    }
    if (state==1) g_lcd.character(8,0,83); // surface icon
    if (state==2 && iseven(seconds)==1) g_lcd.character(8,0,4); // diving icon - inverse D
    if (state==2 && iseven(seconds)==0) g_lcd.character(8,0,68); // diving icon - normal D
    */
// yes - this does nothing as all this is now done by vmessage
}

// warning and LED conditions

void warning() {
    if (depth>=mod && flash==1) g_lcd.character(11,0,33);
    else g_lcd.character(11,0,32); // blank sapce

}

// pick maximum of two values
float maximum(float a,float b,float c) {
    float maximum;
    if(a>b && a>c) maximum=a;
    if(b>a && b>c) maximum=b;
    if(c>a && c>b) maximum=c;
    return(maximum);
}

// pick minimum  of three values
float minimum(float a,float b,float c) {
    float minim;
    if (a<b && a < c) minim=a;
    if(b<a && b<c) minim=b;
    if(c<a && c <b) minim=c;

    return(minim);
}


// handset led control
void leds() {
// first turn everything off
    red=0;
    green=0;
    blue=0;
    float ppox,ppom,sp;
    int mo=0;
    //mo=MODE;
    if(setpoint==0) sp=lowsetpoint;
    else sp=highsetpoint;
    ppox=maximum(ppo1,ppo2,ppo3); // use max value to compute leds...
    ppom=minimum(ppo1,ppo2,ppo3); // unless we want minimum
    if (mo==0) { // CCR mode
        if (ppom<0.2 && flash==1) {red=1;} // flashing red means very bad things - getting very --low on oxygen!!!
        if (ppom>0.2 && ppox < (sp-0.15)) red=1; // non-flashing red
        if (ppox>=(sp-0.15) && ppox <(sp-0.5)) {
            red=1;    // red-green
            green=1;
        }
        if (ppox<(sp+0.05) && ppox >=(sp-0.05)) green=1; // green - optimal range in ccr mode
        if (ppox<(sp+0.15) && ppox >=(sp+0.05)) {
            green=1;    // green-blue - high ppo2 be careful of spiking
            blue=1;
        }
        if (ppox<1.6 && ppox>=(sp+0.15)) blue=1; // DANGER blue high ppo2
        if (ppox>=1.6 && flash==1) blue=1;
    }
   /*if (mo==1) { // SCR mode
        if (ppom<0.2 && flash==1) red=1;
        if (ppom>=0.2 && ppox <0.26) red=1; // will give green red for low but not lethal ppo2s
        if (depth < 0.8*mod && ppom>0.2) green=1;
        if (depth< mod && depth >=0.8*mod) {
            green=1;
            blue=1;
        }
        if (depth >=mod && flash==1) blue=1;
    }*/

}



//read battery state and insert the battery symbol
void battery() {
 char cgchar[32]={
        6,9,9,9,9,9,9,15, // battery empty symbol         
        6,9,9,9,9,15,15,15, // battery 50% symbol         
        6,9,9,15,15,15,15,15, // battery 75% symbol       
        6,15,15,15,15,15,15,15, // battery 100% symbol    
        };


    int batsym=0,i=0; // battery < 3.85V


    // idea to build in 6 levels of battery indicator by on the fly reprogramming character 2 as required.
    Vb=0;
    for (i=0; i<4; i++) {
        Vb=Vb+Vbatt; // read adc connected to battery via a 1/3 potential divider
        wait(0.05);
    }
    Vb=Vb/4; // average 4 readings to reduce noise

    if (Vb>0.388) batsym=8; //3.85-3.92V
    if (Vb>0.395) batsym=16; // battery 3.92-4V
    if (Vb>0.404) batsym=24; // battery . >4V


// write the appropriate battery symbol into the first custom character
    g_lcd.writeCommand(0x40); // set start address for CGRAM
    for (i=0; i<8; i++) {
        g_lcd.writeData(cgchar[i+batsym]);
    }

g_lcd.character(11,1,0); // battery symbol
 if (batsym ==0 && flash==0) g_lcd.character(11,1,32); // bung in space if flashing


}

// sub to make the nice stop or no stop message work in locations 9,0 and 9,1
void vmessage() {
int i,d,cpos=0;
// "INITSURFDIVE" in vertical chas - 1 custom char per two symbols
   char mesg[48]={  17,31,17,0,31,6,12,31, 0,17,31,17,0,1,31,1,  19,21,25,0,31,16,31,0,    31,13,23,0,31,5,1,0,   31,17,14,0,17,31,17,0,  15,16,15,0,31,21,17,0};
                  
   
 
   g_lcd.writeCommand(104); // set start address for CGRAM characrter #6 out of 8

    for (i=0; i<16; i++) { // write 2 chars worth into this segment NO DECO
   

       g_lcd.writeData(mesg[i+state*16]);
    } // endfor


    
g_lcd.character(12,0,5); // custom char 5

g_lcd.character(12,1,6); // custom char 6

}


// subroutine to write the main display data
//0123456789abcdef

//x.xx:xx D XX  xx
//x.xx:xx B XX xxx NB the warning, staus and battery icons are driven by separate subroutines.
void display() {
    int mo=0,tmp=0;
    //mo=MODE;
    g_lcd.character(3,1,32);
//1st line

 //ppo1
    if(ppo1<1) tmp=(int)(ppo1*100); else tmp=(int)(ppo1*100-100);
    g_lcd.locate(1,0);
    g_lcd.printf("%02d ",tmp);
    if(ppo1>=1) g_lcd.character(0,0,2);
    if(ppo1<1) g_lcd.character(0,0,1);
    
    if(ppo2<1) tmp=(int)(ppo2*100); else tmp=(int)(ppo2*100-100);
    g_lcd.locate(5,0);
    g_lcd.printf("%02d ",tmp);
    if(ppo2>=1) g_lcd.character(4,0,2);
    if(ppo2<1) g_lcd.character(4,0,1);
    
    if(ppo3<1) tmp=(int)(ppo3*100); else tmp=(int)(ppo3*100-100);
    g_lcd.locate(9,0);
    g_lcd.printf("%02d ",tmp);
    if(ppo3>=1) g_lcd.character(8,0,2);
    if(ppo3<1) g_lcd.character(8,0,1);
    
    g_lcd.locate(13,0);
    g_lcd.printf("%.2d",(int)depth); // depth
    g_lcd.locate(4,1);
    g_lcd.printf("%2dm",(int)mod); // mod
//2nd line
    g_lcd.locate(0,1);
    tmp=minimum((float)fo1,(float)fo2,(float)fo3); // get min fraction of oxygen to display lowest for deco use
    g_lcd.printf("%2d%%",tmp);
    g_lcd.locate(13,1);
    g_lcd.printf("%03d",scrubtime % 1000); // modulo to avoid digits conflict - means divetime is always less than 100
    g_lcd.locate(8,1);
    g_lcd.printf("%2d",(int)(divetime/60) % 100 ); // rolls back to zero if go over 99
    // bung in battery icon
    battery();
    status(); // this will set the diving / suface mode icon
 //   warning(); // this will set the warning ic on assuming that max ppo2 is exceeded
    g_lcd.character(7,1,32); // space to cover any write error on top line
    leds(); // this sets the leds according to the various warning conditions
   /* if (mo==0) {
        g_lcd.character(7,1,99);    //'c' = ccr
    } else {
        g_lcd.character(7,1,115);    //'s' = scr
    }*/
    // custom character setting to sort out dp in depths


    char cgchar[80]={
        7,5,5,5,23,0,0,0, // .0
        2,2,2,2,18,0,0,0, //  .1
        7,1,7,4,23,0,0,0, // .2
        7,1,3,1,23,0,0,0, // .3
        5,5,7,1,17,0,0,0, //.4
        7,4,7,1,23,0,0,0, //.5
        7,4,7,5,23,0,0,0, //.6
        7,1,2,2,18,0,0,0, //.7
        7,5,7,5,23,0,0,0, //.8
        7,5,7,1,17,0,0,0 //.9

    };
// special dp digit for depth
    int i=0,d=0;
    d=(int)((depth-(int)depth)*10); // should be size of the 1st decimal place
// do stuff here to set cstom chars
    g_lcd.writeCommand(120); // set start address for CGRAM
    for (i=0; i<8; i++) {
        g_lcd.writeData(cgchar[i+d*8]);
    }

    g_lcd.character(15,0,7); // put in appropriate custom character
    
    // display the current setpoint information
    if(setpoint==0)    g_lcd.character(7,1,218); // letter down arrow for low setpoint
    if(setpoint==1)    g_lcd.character(7,1,217); // Letter 'H'
    if(flash==1)  g_lcd.character(7,1,115); // Letter ':'

}




// read sensors and generate calibrated outputs NB battery is read elsewhere

void readsensors() {
    float barometric=0,mod1,mod2,mod3,temp,Vdepth=0,s1,s2,s3,MPXref=0;
    int tc=0;
  //  ppo1=EG1*0.21/eg1cal; // eg1cal is 0.21bar ppO2
  //  ppo2=EG2*0.21/eg2cal; // second oxygen cell ppO2
  //  ppo3=EG3*0.21/eg3cal;
   
    s1=0;
    s2=0;
    s3=0;
    for(tc=0;tc<20;tc++) // noise on Vdepth so average readings to compensate
    { 
    Vdepth=Vdepth+(PRESin/DRATIO); // read the depth sensor and calculate the real value rather using the dividing ratio
    wait_ms(10); // slows stuff down a bit but not a big problem
    s1=s1+EG1; // read o2 sensors
    s2=s2+EG2;
    s3=s3+EG3;
    MPXref=MPXref+V5V; // read 5V
    wait_ms(10); // slows stuff down a bit but not a big problem
    }
    Vdepth=Vdepth/20; // now have the average
    s1=s1/20-coff1;
    s2=s2/20-coff2;
    s3=s3/20-coff3;
    MPXref=MPXref/20*3.3*2; // should be 5V

    
    // compute ppO2s
    ppo1=s1*0.21/eg1cal;
    ppo2=s2*0.21/eg2cal;
    ppo3=s3*0.21/eg3cal;
    
    pc.printf("EG1=%f\tEG2=%f\tEG3=%f   \tMPXref=%f                             \r",s1,s2,s3,MPXref);
    pressure=(Vdepth*3.3/MPXref-0.04)/0.0012858; // pressure in kpa NB - assums that the 5V is correct
    //pressure=(PRESin*3.3/0.65006-0.04)/(0.0012858); // pressure in kPa assuming standard cal for mpx5700 sensor SUSPECT
    // NB the mpx5700 runs off 5v so would be better to divide its output down by 3/5 to get full range measurement
    //outputs. with no division the max mbed adc of 3.3V coresponds to 480kpa or about 38m depth.....
    // standard spec on mpx5700 should be 5V*(P*0.0012858+0.04)
    // new sensor has 3/5 divider built into sensor wiring.
    //barometric=(pcal*3.3/0.65006-0.004)/(0.0012858); // sealevel in kPa assuming standard cal for mpx5700 sensor
    barometric=(pcal*3.3/MPXref-0.04)/0.0012858; // barometric pressure (kpa) evaluated from calibration which we assume is baseline
    depth=(pressure-barometric)*0.1;   //100kPa=10m 1kPa=0.1m - this gives depth in m for freshwater.
    
    if (depth<0) depth=0; // deals wtih noise that may lead to small variation in values

// THESE SHOULD BE JUST 100*ppox/(pressure/100);
    fo1=100*ppo1/((pressure-barometric)/100+1); // pressure in bar = pressure /100 and want a % so multiply by 100 as well
    fo2=100*ppo2/((pressure-barometric)/100+1);
    fo3=100*ppo3/((pressure-barometric)/100+1); // maybe these should be ppox/(depth/10+1)*100....?

    /*if (fo1<0) fo2=0;
    if (fo2<0) fo1=0;
*/
    //with three sensors will calculate mod from the largest ppo2 reading
    mod1=(1.4/(fo1/100)-1)*10;
    mod2=(1.4/(fo2/100)-1)*10;
    mod3=(1.4/(fo3/100)-1)*10;

    mod=minimum(mod1,mod2,mod3); // pick the least value
    
   // output for debugging to pc via usb line.
  //  pc.printf("VDepth %f\tPressure %f\tbarometric %f\tdepth %f\t  \n\r",Vdepth,pressure,barometric,depth);
    //NB - problem - we really need to monitor the 5V power line to ensure that it's driving the depth sensor ok.
    // it may need thicker cables as it currently only manages 4.8V on the board when everything is running.

}
// get values back from cal file on local drive
void recall() {
    FILE *fp=fopen("/local/CAL.dat","r");
    fscanf(fp,"%e\n%e\n%e\n%e\n%d",&eg1cal,&eg2cal,&eg3cal,&pcal,&scrubold);
    fclose(fp); //NB file system locked on write so must make sure we close files in case want to reprogram etc...
}

// write the logfile opened and closed by start and end of dive
void store_log() {

    //FILE *fp=fopen("/local/divelog.dat","a");
    float v5=0;
    v5=V5V;
    fprintf(lp,"%d\t%e\t%e\t%e\t%e\t%e\t%e\t%d\n",seconds,depth,ppo1,ppo2,ppo3,Vb,v5,scrubtime);

    if (divetime % 60==0) fflush(lp);
    // fclose(fp);
}

// read switches and report state
int switches() {
    int ss=0;
    if (SW1==1 && SW2==1) ss=3;
    if (SW2==1 && SW1==0) ss=2;
    if (SW1==1 && SW2==0) ss=1;
    return(ss);
}

// interpret the ppo2 data into a simple set of hud codes.
int HUD_display()
{ 
    int i,j3,h1,h2,h3;
    float cset=0;
      char gcode[6]={0,1,3,2,6,4}; // grey code for HUD reading 'red,amber,green,cyan,blue'
    
    HUD_clr(); // clear the HUID ready for write
   
    
    if(setpoint==0)  // low setpoint
    {
    cset=lowsetpoint;
    }
    
    if(setpoint==1) // hgh setpoint
    {
    cset=highsetpoint;
    }

    h1=(int)((ppo1-cset)/0.1+3.5);
    if(h1<1) h1=1;
    if(h1>5) h1=5;
    h2=(int)((ppo2-cset)/0.1+3.5);
    if(h2<1) h2=1;
    if(h2>5) h2=5;
    h3=(int)((ppo3-cset)/0.1+3.5);
    if(h3<1) h3=1;
    if(h3>5) h3=5;
    
    if(h3>3) btest=0; // handle extra blue connected to btest setting btest low lights blue led
    
    
    i=gcode[h1]+8*gcode[h2]+64*gcode[h3]; // this is possible bigger than a char so have to typeconvert
    HUD_write(i);
       
}

// sub to flash HUD n times as a warning of setpoint shift
int HUD_flash(int n)
{
    int i;
    for(i=0;i<n;i++)
    {
    HUD_clr();
    wait(0.2);
    HUD_white();
    wait(0.05);
    }
}

int setswitch()
{
    if(setpoint==0 && depth >(switchdepth+0.5)) 
    { 
        setpoint=1; // handle switch from low to high
        HUD_flash(4);
        // flash the hud here
    }
    
    if(setpoint==1 && depth < (switchdepth -0.5))
    {
        setpoint=0;  // swtich to low setpoint
        HUD_flash(2);
        // flash the HUD here
    }
}

int main() {
// first some local variables
    int startuptime=getseconds();
    int startdive=0,endclock=0; // value of seconds when dive starts and counter to decide if dive complete...

    int minutes=0; // minutes is elapsed minutes since start of prog
    int j=0,scount=1;; // general loop counting variable
    int sw=0; // status of the switches in the handset
    char schars[4]={32,0xd9,0xda,0xfb}; // up arrow, down arrow and both arrows;

    bool mdir=0;

    set_custom_char(); // does what it says on the tin really
    g_lcd.cls();
    g_lcd.locate(0, 0);
    g_lcd.printf( "RebMon");
    g_lcd.locate(0,1);
    g_lcd.printf("CAL?");
    battery();
    j=0;
wait(0.2);

    // get cal values last used from local drive
    recall();
    // display the correct scrubber time
    scrubtime=scrubold;
// hang about waiting for the cal switch to be pressed in ccase it is
    while (scount<30) {
        seconds=getseconds(); // NB if this hangs then nothing works :( - usually means 5V is dodgy
        red=1;
        green=1;
        blue=1; // light all leds

        g_lcd.locate(5,1);
        g_lcd.printf("%.2d ",30-scount);
        if (j>1) flash=1;
        else flash=0;
        battery(); // bung in battery symbol.
        g_lcd.locate(7,0);
        g_lcd.printf( "%.2d:%.2d:%.2d", hours,min,sec);
      //  if (CAL==0) {
        if(SW1==0) {
            calibrate();
            scount=31; // make sure it goes to next frame ok
        }
        wait(0.05);


        j=(j+1) % 4;
        if(flash==0) HUD_white();
        else HUD_clr();
        scount++;
    } 
    g_lcd.cls();


    // ok there are three states in this system
//MAIN LOOP ONCE STARTUP PROTOCOLS ARE COMPLETED
    j=0;
    g_lcd.cls();
    while (1) {
        wait(0.1); //stop screen flicker
        readsensors();
        setswitch(); // check the setpoint and adjust if crossing the swtich depth
        HUD_display(); // write the HUD codes
        seconds=getseconds();
        minutes=(int)(((float)seconds-(float)startuptime)/60);
        led1=seconds % 2; // flash the onboard led to make it clear stuff is running

        if (j>1) flash=1;
        else flash=0;

        display(); // write the display
        HUD_display(); // write the HUD
      // sw=switches(); // read the switches and report their state
      // if(SW1==0) g_lcd.character(11,0,0xEF); else g_lcd.character(11,0,32); // NB here is possible alternate display underwater switching point
        // HERE do deco calcs - update tissues and compute desat , nostop or ascent times as required.

        // setup state variable
      if (minutes<1) state=0; // startup mode - do nothing just wait to allow sensor readings to settle.
        if (minutes>=1 && state==0) state=1; // surface mode - ok to go for a dive now
        if (minutes>=1 && depth>0.8 && state==1) {
            state=2; // enter dive mode
            lp=fopen("/local/divelog.dat","a");
            fprintf(lp,"#startdive = %d\n#seconds\tdepth\tppo1\tppo2\tppo3\tVb\t\tV5V\tscrubtime\n",seconds); // bung in a header here in case one needs it
            store_log(); // make a first log entry to catch this erliest part of the dive
            if (startdive==0) startdive=seconds; // set start of divetime. don't do this twice
            endclock=0; // reset end of dive clock
        }
        // in dive mode - things to do, 1 update divetime and scrubber time, 2 write log data 3 check for end of dive...
       if (state==2) {
            // divetime=(int)(((float)seconds-(float)startdive)/60.0); // time since start of dive in minutes.
            divetime=(seconds-startdive); // divetime no recorded in seconds since start of dive

            // do deco calcs here when implemented
            if (divetime %15 ==0) store_log(); // this saves the dive profile data every 15s and sensor optputs in a file called divelog.dat
            if (depth<=0.5) {
                endclock=endclock+1;

                if (endclock>150) {
                    state=1; // 30s at shallower than 0.5m and we return to surface mode. */
                    FILE *fp=fopen("/local/CAL.dat","w");
                    fprintf(fp,"%e\n%e\n%e\n%d",eg1cal,eg2cal,pcal,scrubtime);
                    fclose(fp); //NB file system locked on write so must make sure we close files in case want to reprogram etc...
                    
                    store();
                    fclose(lp);
                } // endif endclock
                   
            } // end if depth
            scrubtime=scrubold+(divetime/60); //
        } // end state 2


        j=(j+1) %4; // flash control variable = used to make the warnings flash for 0.4s duty cycle


        vmessage(); // call to generate status message in vertical segment
    } // end while
} //end main