Firmware for sample prep heater device

main.cpp

Committer:
marcbax
Date:
2011-12-05
Revision:
0:c201d3f2c1f8

File content as of revision 0:c201d3f2c1f8:

//Firmware for P173_PoopCooker sample preparation heater block

#include "mbed.h"
char* fwversion="0.71";                                             //this version published 5/9/2011            

//Pin and object assignments
DigitalOut led1(LED1), led2(LED2), led3(LED3), led4(LED4);
DigitalOut buzzer(p30);
AnalogIn ntc(p19);
DigitalOut heater(p18);
Serial display(p13, p14);
InterruptIn keyup(p7), keyright(p5), keydown(p6);
InterruptIn keyleft(p9), keyOK(p8);
DigitalOut fan(p24);
PwmOut rled(p21), gled(p22), bled(p23);
Timer kitchentimer;
Timeout kitchenalarm;
Timeout heateroff;
Ticker showtime;
Ticker heatercontrol;

// Global constants definitions

float heaterperiod = 5;                                             //cycle time for heater control
float propterm = 0.1;                                               //proportional term in PID algorithm
float intterm = 0.001;                                              //integral term in PID algorithm
float difterm = 0;                                                  //differential term in PID algorithm

float ntcB = 3988;                                                  //beta value for exponential NTC calculation
float ntcR25 = 10000;                                               //resistance value of NTC at 25 degC
float rdivntc = 3300;                                               //resistance value of upper ladder resistor for NTC

float maxblocktemp = 115;                                           //block temperature cutout
float minblocktemp = 25;                                            //25 degC minimal setpoint temp
float defaultblocktemp = 60;                                        //this needs to be changed to the standard extraction temperature    
int maxcountdown = 6540;                                            //99 minutes max countdown
int defaultcountdown = 600;                                         //10 minutes default countdown

// Global variable definitions

float btemp;                                                        //current hot block temperature in degC
float blocksettemp;                                                 //hot block setpoint temperature
float acterror, interror, diferror;                                 //error terms for the PID algorithm
float ntcA;                                                         //calculated from ntcB and ntcR25
int countdownset;                                                   //kitchen timer setting in seconds
int remainingtime;                                                  //kitchen timer current remaining time
char ntcerror;                                                      //0 = no error, 1 = open circuit, 2 = short circuit
float LedRGB[3] = {0, 0, 0};                                        // PWM dutycycle red, green, blue
bool kitchentimerset;                                               // true if kitchen countdown timer is set
char lastkeypressed;                                                // 1=up, 2=right, 3=down, 4=left, 5=OK
bool beepsdone;


// Function definitions

// Clears and homes the display
void cleardisplay() {
    display.printf("\e[2J\e[H");
}

// Short beep
void beeponce() {
    buzzer = 1;
    wait(0.1);
    buzzer = 0;
}

// beep beep beep
void multibeeps(int number, float interval) {
    for(int i=0;i<number;i++){
        beeponce();
        wait(interval);
    }
}

// Device power up routine
void initdevice() {

    //set all outputs to "off"
    led1=led2=led3=led4=0;
    buzzer=0;
    heater=0;
    fan=0;
    bled=0;
    rled=0;
    gled=0;
    rled.period_us(100);                                            //PWM frequency set to 10kHz
    ntcerror=0;
    beepsdone=1;
    
    //key pull-up modes
    keyup.mode(PullUp);
    keyright.mode(PullUp);
    keydown.mode(PullUp);
    keyleft.mode(PullUp);
    keyOK.mode(PullUp);
    
    //calculate ntcA
    ntcA = ntcR25/exp(ntcB/298);                                    //298K = 25degC
    
    //reset error terms
    acterror = interror = diferror = 0;
    
    //default block temp setting
    blocksettemp = defaultblocktemp;
    
    //initialise kitchentimer
    countdownset = defaultcountdown;
    kitchentimerset=0;
    
    //initialise the display and show splash screen
    display.baud(19200);                                            //set display baud rate to 19200
    wait(0.2);                                                      //display power-on delay
    display.putc(0x0d);                                             //send CR to display - this needs to be first character sent after power-up
    wait(0.2);                                                      //wait for display to adjust to serial baud rate
    display.printf("\e[4L\e[20C\e[2m");                             //set display to 4 lines of 20 characters, cursor off
    cleardisplay();                                                 //clear screen and home cursor
    display.printf("\e[2;2HLumora Sample Prep");                         //display splash screen
    display.printf("\e[3;4HFirmware: %s", fwversion);                    //display firmware version
    wait(3);                                                        //time that splash screen is shown
    cleardisplay();
    beeponce();

}

// Functions called by key interrupts
void nothing() {                                                    //this is a no-operation function, used to detach an InterruptIn
}

void int_keyup() {
    lastkeypressed = 1;
    beeponce();
}

void int_keyright() {
    lastkeypressed = 2;
    beeponce();
}

void int_keydown() {
    lastkeypressed = 3;
    beeponce();
}

void int_keyleft() {
    lastkeypressed = 4;
    beeponce();
}

void int_keyOK() {
    lastkeypressed = 5;
    beeponce();
}

void incrementsettemp() {
    if (blocksettemp < maxblocktemp) blocksettemp = blocksettemp + 1;
    beeponce();
}

void decrementsettemp() {
    if (blocksettemp > minblocktemp) blocksettemp = blocksettemp - 1;
    beeponce();
}

void incrementtimer() {
    if (countdownset < maxcountdown) countdownset = countdownset + 60;
    beeponce();
}

void decrementtimer() {
    if (countdownset > 60) countdownset = countdownset - 60;
    beeponce();
}

// Display update routine (also handles alarm)
void updatedisplay() {
    display.printf("\e[2;3HBlock Temp %3.0f%cC", btemp, 223);
    remainingtime = countdownset - kitchentimer.read();
    if (kitchentimerset) {
        display.printf("\e[3;3HCountdown  %02u:%02u", remainingtime/60, remainingtime%60);
    }
    else {
        display.printf("\e[3;3HSet Timer  %02u:%02u", remainingtime/60, remainingtime%60);
    }
    if (remainingtime <= 0) {                                       //alarm condition
        kitchentimer.stop();                                        //stop timer
        kitchentimerset=0;                                          //clear flag
        if (!beepsdone) multibeeps(3,0.25);                         //duration of multibeeps needs to be less then 0.9 seconds
        beepsdone = 1;                                              //set beepsdone to only sound alarm once
    }
}

// Stops the countdown timer
void stopkitchentimer() {
    beeponce();
    if (kitchentimerset) {
        kitchentimer.stop();
    }
    else {
        kitchentimer.reset();
    }
    kitchentimerset=0;
}

// Starts the countdown timer
void startkitchentimer() {
    kitchentimer.start();
    beeponce();
    beepsdone=0;
    kitchentimerset = 1;
}

// Returns the current hot block temperature in degrees centigrade as measured by the NTC
float blocktemp() {
    float vntc, rntc;
    wait(0.03);
    vntc = ntc.read()*3.3;
    if (vntc > 3) {                                                 //check for NTC open circuit
        ntcerror=1;
        return 120;                                                 //on error return high temperature
    }
    else {
        if (vntc < 0.3) {                                           //check for NTC open circuit
            ntcerror = 2;
            return 120;                                             //on error return high temperature
        }
        else {
            rntc = (vntc * rdivntc)/(3.3 - vntc);                   //calculate Rntc from Vntc
            return ntcB/(log(rntc)-log(ntcA))-273;                  //calculate temperature using exponential approximation curve (beta curve)
        }
    }
}

// Returns the next duty-cycle for the heater using a PID algorithm
float getheaterPWM() {
    float prevtemp, outpt;
    prevtemp = btemp;                                               //store previous temperature to calculate differential
    btemp = blocktemp();                                            //measure current block temperature
    if (btemp < (blocksettemp - 10)) {                              //heater constantly on when temperature way too low, no error build-up
        return 1;
    }
    else {                                                          //only build up integral error when temperature is within range
        acterror = blocksettemp - btemp;                            //calculate current error term
        interror = interror + acterror;                             //update integral error term
        diferror = btemp - prevtemp;                                //calculate differential error term
        outpt = (propterm * acterror);                              //calculate and set proportional contribution
        outpt = outpt + (intterm * interror);                       //calculate and add integral contribution
        outpt = outpt + (difterm * diferror);                       //calculate and add differential contribution
        if (outpt <0) outpt=0;                                      //minimum output is "off"
        if (outpt >1) outpt=1;                                      //maximum output is "on"
    }
    if (btemp > maxblocktemp) outpt=0;                              //software overtemperature cutout
    return outpt;
}

//Translates block temperature to RGB value of indication LEDs
void temp2RGB (float temp) {
    if (temp < 40) {                                                //LEDs show pure blue for low temperature
        LedRGB[0] = 0;
        LedRGB[1] = 0;
        LedRGB[2] = 0.5;
    }
    else {
        if (temp > 60) {                                            //LEDs show pure red for high temperatures
            LedRGB[0] = 1;
            LedRGB[1] = 0;
            LedRGB[2] = 0;
         }
         else {                                                     //LEDs mix red and blue
            LedRGB[0] = (temp-40)/20;
            LedRGB[1] = 0;
            LedRGB[2] = (60-temp)/20;
         }
     }
}

// Drives the temperature indication LEDs in the top plate
void driveRGBled() {                                                //uses global array, as functions cannot pass array variables
    rled = LedRGB[0];
    gled = LedRGB[1];
    bled = LedRGB[2];
}

// Set or adjust the block setpoint temperature
void adjustblocktemp () {
    keyup.fall(&incrementsettemp);                                  //define "up" key function
    keydown.fall(&decrementsettemp);                                //define "down" key function
    keyOK.fall(&int_keyOK);                                         //define "OK" key function
    display.printf("\e[2;6HSettings");
    while (lastkeypressed != 5) {                                   //loop until OK is pressed
        display.printf("\e[3;3HSet temp= %3.0f%cC", blocksettemp, 223);
        wait(0.2);
    }
    keyup.fall(&nothing);                                           //detach InteruptIn functions
    keydown.fall(&nothing);
    keyOK.fall(&nothing);
    lastkeypressed = 0;                                             //clear flag
}

// Warm-up to operating temperature
void warmupblock (float settemp) {
    btemp = blocktemp();
    float heaterdutycycle;
    cleardisplay();
    if (settemp < maxblocktemp) {                                   //this test avoids overtemperature due to incorrect function call    
        while (btemp < (settemp -0.5)) {                            //setpoint reached if temperature within 0.5 degrees
            btemp = blocktemp();
            display.printf("\e[2;2HWarming up: %3.0f%cC", btemp, 223);
            temp2RGB(btemp);
            driveRGBled();
            heaterdutycycle = getheaterPWM();                       //PWM heater with longish period    
            heater = 1;
            wait (5 * heaterdutycycle);
            heater = 0;
            wait (5 * (1 - heaterdutycycle));
        }
    }
    heater = 0;
}

// Cooling down
void coolblockdown() {
    showtime.detach();                                              //avoid showing timer on display
    while (1) {
        cleardisplay();
        btemp = blocktemp();
        fan = (btemp > 40);                                         //fan is on when block temperature above 40 degrees
        display.printf("\e[2;3HCool down: %3.0f%cC", btemp, 223);
        temp2RGB(btemp);
        driveRGBled();
        if (!fan) display.printf("\e[3;3HSafe to touch");
        wait(5);                                                    //one update per 5 seconds is more then enough
    }
}

// Regulating to a constant temperature
void stayhot() {
    float heaterdutycycle;
    cleardisplay();
    showtime.attach(&updatedisplay, 1.0);                           //set ticker for routine that updates display every second
    keyOK.fall(&startkitchentimer);                                         //define key interrupt routines
    keyright.fall(&int_keyright);
    keyup.fall(&incrementtimer);
    keydown.fall(&decrementtimer);
    keyleft.fall(&stopkitchentimer);
    while (lastkeypressed != 2) {                                   //main program loop until OK key pressed
        btemp = blocktemp();
        temp2RGB(btemp);
        driveRGBled();
        heaterdutycycle = getheaterPWM();                           //PWM heater with longish period
        heater = 1;
        wait (5 * heaterdutycycle);
        heater = 0;
        wait (5 * (1 - heaterdutycycle));
    }
    stopkitchentimer();                                             //stop timer
    keyOK.fall(&nothing);                                           //detach key interrupt routines
    keyright.fall(&nothing);
    keyup.fall(&nothing);
    keydown.fall(&nothing);
}

// Main program
int main() {
    initdevice();                                                   //power-on initialisation
    btemp = blocktemp();
    adjustblocktemp();
    beeponce();
    warmupblock(blocksettemp);                                      //warming up
    stayhot();                                                      //this is where to program spends most of its time
    coolblockdown();                                                //fan-assisted cool down
}