New

Dependencies:   Menu PID_v2 PinDetect QEI TextLCD Thermistor mbed

main.cpp

Committer:
albertobianco
Date:
2016-06-09
Revision:
4:800e1df45650
Parent:
3:884bfa90a09d

File content as of revision 4:800e1df45650:

#include "mbed.h"
#include "TextLCD.h"
#include "PID.h"
#include "QEI.h"
#include "Menu.h"
#include "PinDetect.h"
#include "Thermistor.h"

#define TPIT_MIN     0.0
#define TPIT_MAX     250.0
#define TCOAL_MIN     0.0
#define TCOAL_MAX     1000.0
#define TMEAT_MIN    50
#define TMEAT_MAX    120
#define MAX_HOURS    24

#define PULLUP_MEAT 10000.0

/*

 This FW implements a PID temperature controller specifically designed for Ugly Drum Smokers (UDS)
 and uses a K-type termocouple as pit temperature sensors and a PWM-controlled fan to regulate the
 airflow at the intake.
 A second K-type termocouple is used to monitor the meat temperature, while a countdown timer takes care
 of the cooking time.
 The required HW consists of:
 - 1x STM32 F411RE Nucleo board
 - 1x 20x4 Text LCD
 - 1x Quadrature rotary encoder
 - 1x Pushbutton (if not embedded in the encoder)
 - 2x MAX6675 modules
 - 2x K-type termocouples (pit & meat)
 - 1x Buzzer
 - 1x 12V fan
 - 1x N-MOS capable of driving the fan
 - 1x fly-wheeling diode

*/

int Hrs=0, Min=0, Sec=0, warning=0, timeout=0;
int TimerRunning = 0;
float FanSpeed, TcoalPID, Tcoal, Tpit, Tmeat, Tset;
float Talm = 200.0;
float TcoalSet, DfanSet;
float Vpot;
uint8_t req_LCD_update;

uint8_t displayLine;

int pitPidMode = 0;
int coalPidMode = 0;

char stringa[20];

void UpdatePidMode(void);
void SetCoalTemp(void);
void SetFanDuty(void);
void SetPIDK(void);
void ResetSeconds(void);
void SetPitTemp(void);

float coalKc = 0.1, coalTauI = 6.0;
float pitKc = 0.1, pitTauI = 60.0;

MenuNode MNhead(NULL,"Exit");
MenuNode MNpid(&MNhead,"PID settings");
//MenuNode MNpitap(&MNpid,"Pit Auto PID");
MenuNode MNpitPidTarget(&MNhead,"Pit Target temp",(void *) &Tset,'f',&SetPitTemp,1.0,TPIT_MIN,TPIT_MAX);

MenuNode MNpitPidMode(&MNpid,"Pit PID mode",(void *) &pitPidMode,'i',&UpdatePidMode,1.0,0.0,1.0,(char *[]){"Manual","Auto"});
MenuNode MNcoalPidMode(&MNpid,"Coal PID mode",(void *) &coalPidMode,'i',&UpdatePidMode,1.0,0.0,1.0,(char *[]){"Manual","Auto"});

MenuNode MNpitPidOutput(&MNpid,"Coal Temp (man)",(void *) &TcoalSet,'f',&SetCoalTemp,5.0,TCOAL_MIN,TCOAL_MAX);
MenuNode MNcoalPidOutput(&MNpid,"Fan Speed (man)",(void *) &DfanSet,'f',&SetFanDuty,0.05,0.0,1.0);
MenuNode MNcoalKc(&MNpid,"Coal Kc",(void *) &coalKc,'e',&SetPIDK,1.25,0.0001,100.0);
MenuNode MNcoalTauI(&MNpid,"Coal TauI",(void *) &coalTauI,'e',&SetPIDK,1.25,1.0,1000.0);
MenuNode MNpitKc(&MNpid,"Pit Kc",(void *) &pitKc,'e',&SetPIDK,1.25,0.0001,100.0);
MenuNode MNpitTauI(&MNpid,"Pit TauI",(void *) &pitTauI,'e',&SetPIDK,1.25,1.0,10000.0);

MenuNode MNtmr(&MNhead,"Timer settings");
MenuNode MNtmrRun(&MNtmr,"Timer mode",(void *) &TimerRunning,'i',NULL,1.0,0.0,2.0,(char *[]){"Stop","Countdown", "Timer"});
MenuNode MNtmrHrs(&MNtmr,"Hours",(void *) &Hrs,'i',&ResetSeconds,1.0,0.0,24.0);
MenuNode MNtmrMin(&MNtmr,"Minutes",(void *) &Min,'i',&ResetSeconds,5.0,0.0,55.0);

MenuNode MNtalm(&MNhead,"Meat Tgt temp", &Talm,'f', NULL, 1.0, 30.0, 150.0);

Menu MN(&MNhead);

Thermistor MeatThermistor(25.0, 100000.0, 3950.0);


//DigitalOut  RLED    (PC_5);     // Pin 2 of CN10 (MORPHO connector, debug only)
//DigitalOut  YLED    (PC_6);     // Pin 4 of CN10 (MORPHO connector, debug only)
//DigitalOut  GLED    (PC_8);     // Pin 6 of CN10 (MORPHO connector, debug only)

PwmOut      FAN     (D5);       // D3=PB3 --> PMW output (FAN control)

DigitalOut  TC_CLK  (D4);       // D4=PB5 --> MAX6675 CLK (to both chips)

DigitalOut  TC_CS1  (A5);       // A0=PC0 --> MAX6675 CS (chip 1)
DigitalOut  TC_CS2  (D2);       // D2=PA10 --> MAX6675 CS (chip 2)
DigitalIn   TC_SDO  (D8);       // D8=PA9 <-- MAX6675 SDO (from both chips)

//DigitalOut  BUZZER  (A2);       // A2=PA4 --> self-oscillating buzzer
PwmOut      ALM     (D9);

PinDetect Button  (D7);       // D7=PA8 <-- Hold/Set pushbutton

AnalogIn MeatIn (A0);

PID pitPID(0.1, 60.0, 0.0, 1.0);   // Specify Kc, Ti, Td & refresh rate
PID coalPID(0.1, 6.0, 0.0, 1.0);   // Specify Kc, Ti, Td & refresh rate
QEI Wheel(D3, D6, NC, 16, QEI::X4_ENCODING);          // quadrature encoder
TextLCD MyLCD(D15, D14, D13, D12, D11, D10, TextLCD::LCD16x2); //20x4 LCD connection

Ticker Sec_Beat;        // timer ticker
//Ticker decSecBeat;     // 1/10th sec beat


// ------------------- Prototypes -----------------------

void Button_Pressed(void);
void Timer_tick(void);
void Update_LCD(void);
int MAX6675_GetTemp(int);

void ResetSeconds(void)
{
    Sec = 0;
}

// -------------------- Routines ------------------------
void SetPIDK(void)
{
    pitPID.setTunings(pitKc, pitTauI, 0.0);
    coalPID.setTunings(coalKc, coalTauI, 0.0);
}

void SetCoalTemp(void)
{
    if(!pitPID.isAuto())
        pitPID.setOutput(TcoalSet);
}

void SetPitTemp(void)
{
    pitPID.setSetPoint( Tset );
}

void SetFanDuty(void)
{
    if(!coalPID.isAuto())
        coalPID.setOutput(DfanSet);
}



void Button_Pressed()
{
    
    if(!MN.isLeaf())
        MN.descend();
    else
    {
        if(MN.isSelect())
            MN.setEdit();
        else if(MN.isEdit())
        {
            MN.resetEdit();
            MN.execFun();
        }
    }
    
    req_LCD_update = 1;    
    Wheel.reset();
    
}


void UpdatePidMode(void)
{
    coalPID.setMode(coalPidMode);
    pitPID.setMode(pitPidMode);
}

// -------------------------------------------------------------

void Timer_tick()
{
    float Rmeat;
    
    if((Hrs==0)&&(Min==0)&&(Sec==0) && (TimerRunning == 1))
        timeout = 1;
    else 
    {
        timeout = 0;

        if (TimerRunning == 1) 
        {
            Sec--;
            if((Hrs==0)&&(Min==0)&&(Sec==0))
                timeout = 1;
            else if((Sec<=0)||(Sec>60)) 
            {
                Sec=59; 
                if(Min>0)
                    Min--;
                else if (Hrs>0) 
                {
                    Min=59;
                    Hrs--;
                }
            }
        }
        else if ((TimerRunning == 2) ) 
        {
            Sec++;
            if((Sec>=60)) 
            {
                Sec=0;
                Min++; 
                if(Min>=60)
                {
                    Min = 0;
                    Hrs++;
                }                                                  
            }
        }
    }

   Tcoal = (float)(MAX6675_GetTemp(0)/2); // read meat & pit temperature
   Tpit = (float)(MAX6675_GetTemp(1)/2);
    
    pitPID.setProcessValue(Tpit);
    TcoalPID = pitPID.compute();
    coalPID.setSetPoint( TcoalPID );
    
    coalPID.setProcessValue(Tcoal);
    FanSpeed = coalPID.compute();
    
    FAN.write(FanSpeed);
    
    if(MN.isHead())
        req_LCD_update = 1;


    Rmeat = MeatIn.read();        
    Rmeat = (Rmeat * PULLUP_MEAT) / (1 - Rmeat);
    
    Tmeat = MeatThermistor.trans_R2T(Rmeat);
    

    if(Tmeat>Talm || timeout)      // Meat is ready!
        warning = 1;
    else
        warning = 0;


    return;
}


// -------------------------------------------------------------
int saturate(int data, int min, int max)
{
    if(data > max)
        return max;
    else if (data < min)
        return min;
    else return data;
}


char * idleString(int n, char *dst)
{
    int tcoal_l, tpit_l, tmeat_l;   

    switch(n)
    {
        
        case 0:
            sprintf(dst,"%02d:%02d:%02d Tgt %02dC",Hrs,Min,Sec,int(Talm));
            break;
        case 1:
            tcoal_l = saturate(int(Tcoal),0,999);
            tpit_l = saturate(int(Tpit),0,999);
            tmeat_l = saturate(int(Tmeat),0,999);
            sprintf(dst,"C%3dC P%3dC M%2dC",tcoal_l,tpit_l,tmeat_l);
            break;
        case 2:
            sprintf(dst,"d%3d CP%4dC    ",int(FanSpeed * 100), int(TcoalPID));
            break;
        case 3:
            sprintf(dst,"Ciao Mamma");
            break;
    }
    
    return dst;
}


void Update_LCD(void)
{
    

    if(MN.isHead())
    {
              
       MyLCD.locate(0,0);
       MyLCD.printf(idleString(displayLine,stringa));
       MyLCD.locate(0,1);
       MyLCD.printf(idleString((displayLine+1)&0x03,stringa));
       
    }
    else
    {
        MyLCD.locate(0,0);
        MyLCD.printf("%-16s",MN.getText());
        if(MN.isSelect())
        {
            MyLCD.locate(0,1);
            MyLCD.printf("%-16s",MN.getNextText());
        }
        else
        {
            MyLCD.locate(0,1);
            MyLCD.printf("%-16s",MN.getDataText(stringa));
        }
    }


    //Button.fall(&Button_Pressed);
    
#if 0


if(warning||timeout) // in case of a warning or timeout
{
    MyLCD.locate(0,3);
    MyLCD.printf(" ---- WARNING! ---- ");

    //FAN.write(0);     // stop the fan (optional)
} else // otherwise
{
    BUZZER = 0;             // buzzer off
}

#endif
}

// -------------------------------------------------------------
int MAX6675_GetTemp(int chip)
{
    int Temp=0, mask=32768;
    int i;

    if(chip==0)
        TC_CS1 = 0; // select chip #1
    else
        TC_CS2 = 0;  // select chip #2
    wait_ms(1);
    for(i=0; i<16; i++) {

        TC_CLK = 1;  // CLK high
        if(TC_SDO == 1 ) Temp = Temp | mask;
        wait_ms(1);
        TC_CLK = 0;  // CLK low
        wait_ms(1);
        mask = mask/2;
    }
    wait_ms(1);
    TC_CS1 = 1;  // Both CS high
    TC_CS2 = 1;

    Temp=((Temp>>3)&0x0FFF);

    return(Temp);

}

// -------------------------------------------------------------
// -------------------------------------------------------------
// -------------------------------------------------------------

int main()
{

    FAN.period_us(500);       // set initial fan PWM period and duty-cycle
    FAN.write(0.1);

    pitPID.setInputLimits(TPIT_MIN, TPIT_MAX);
    pitPID.setOutputLimits(TCOAL_MIN, TCOAL_MAX);
    
    coalPID.setInputLimits(TCOAL_MIN, TCOAL_MAX);
    coalPID.setOutputLimits(0.0, 1.0);
    
    ALM.period_us(500);      // set initial ALM period and duty-cycle
    ALM.write(0.01);

    TC_SDO.mode(PullDown);    // enable pull-down on TC_SDO
    
    Button.mode(PullUp);      // enable pushbutton pull-up
    
    Button.setSampleFrequency( 20000 );
    
    //Wheel.reset();            // reset quadrature encoder
    Hrs = 2;
    timeout = 0;
    
    MyLCD.cls();              // clear LCD

    MyLCD.locate(0,0);
    MyLCD.printf(__TIME__, Hrs, Min, Sec);
    MyLCD.locate(1,1);
    MyLCD.printf(__DATE__, Hrs, Min, Sec);


    Sec_Beat.attach(&Timer_tick, 1);      // configure tickers    
    //decSecBeat.attach(&Fast_tick, 1);      // configure tickers
    FanSpeed = 0;

    //Button.fall(&Button_Pressed);  // enable button interrupt
    Button.attach_deasserted( &Button_Pressed );
    
    while(1)
    {
        if(req_LCD_update) {
            Update_LCD();
            req_LCD_update = 0;
        }
        
        if(warning)
            ALM.write(0.01);
        else
            ALM.write(0.0);
        
        if(MN.isSelect())
        {
            if(Wheel.getPulses() > 0)
            {
                MN.next();
                Wheel.reset();
                req_LCD_update = 1;
            }
            else if(Wheel.getPulses() < 0)
            {
                MN.prev();
                Wheel.reset();
                req_LCD_update = 1;
            }
        }
        else if(MN.isEdit())
        {
            if(Wheel.getPulses())
            {
                MN.edit(Wheel.getPulses());
                Wheel.reset();
                req_LCD_update = 1;
            }
        }
        else if(MN.isIdle())
        {
            if(Wheel.getPulses() > 0)
            {
                displayLine++;
                displayLine &= 0x3;
                Wheel.reset();
                req_LCD_update = 1;
            }
            else if(Wheel.getPulses() < 0)
            {
                displayLine--;
                displayLine &= 0x3;
                Wheel.reset();
                req_LCD_update = 1;
            }
            
        }
    }
    



} //main