void Thermocycler::SetPlateControlStrategy() { if (InControlledRamp()) return; if (absf(iTargetPlateTemp - GetPlateTemp()) >= PLATE_BANGBANG_THRESHOLD && !InControlledRamp()) { iPlateControlMode = EBangBang; iPlatePid.SetMode(MANUAL); } else { iPlateControlMode = EPIDPlate; iPlatePid.SetMode(AUTOMATIC); } if (iRamping) { if (iTargetPlateTemp >= GetPlateTemp()) { iDecreasing = false; if (iTargetPlateTemp < PLATE_PID_INC_LOW_THRESHOLD) iPlatePid.SetTunings(PLATE_PID_INC_LOW_P, PLATE_PID_INC_LOW_I, PLATE_PID_INC_LOW_D); else iPlatePid.SetTunings(PLATE_PID_INC_NORM_P, PLATE_PID_INC_NORM_I, PLATE_PID_INC_NORM_D); } else { iDecreasing = true; if (iTargetPlateTemp > PLATE_PID_DEC_HIGH_THRESHOLD) iPlatePid.SetTunings(PLATE_PID_DEC_HIGH_P, PLATE_PID_DEC_HIGH_I, PLATE_PID_DEC_HIGH_D); else if (iTargetPlateTemp < PLATE_PID_DEC_LOW_THRESHOLD) iPlatePid.SetTunings(PLATE_PID_DEC_LOW_P, PLATE_PID_DEC_LOW_I, PLATE_PID_DEC_LOW_D); else iPlatePid.SetTunings(PLATE_PID_DEC_NORM_P, PLATE_PID_DEC_NORM_I, PLATE_PID_DEC_NORM_D); } } }
void Thermocycler::SetPlateControlStrategy() { if (InControlledRamp()) return; if (fabs(m_target_plate_temp - GetPlateTemp()) >= PLATE_BANGBANG_THRESHOLD && !InControlledRamp()) { m_plate_control_mode = EBangBang; m_plate_pid->SetMode(MANUAL); } else { m_plate_control_mode = EPIDPlate; m_plate_pid->SetMode(AUTOMATIC); } if (m_is_ramping) { if (m_target_plate_temp >= GetPlateTemp()) { m_is_decreasing = false; if (m_target_plate_temp < PLATE_PID_INC_LOW_THRESHOLD) m_plate_pid->SetTunings(PLATE_PID_INC_LOW_P, PLATE_PID_INC_LOW_I, PLATE_PID_INC_LOW_D); else m_plate_pid->SetTunings(PLATE_PID_INC_NORM_P, PLATE_PID_INC_NORM_I, PLATE_PID_INC_NORM_D); } else { m_is_decreasing = true; if (m_target_plate_temp > PLATE_PID_DEC_HIGH_THRESHOLD) m_plate_pid->SetTunings(PLATE_PID_DEC_HIGH_P, PLATE_PID_DEC_HIGH_I, PLATE_PID_DEC_HIGH_D); else if (m_target_plate_temp < PLATE_PID_DEC_LOW_THRESHOLD) m_plate_pid->SetTunings(PLATE_PID_DEC_LOW_P, PLATE_PID_DEC_LOW_I, PLATE_PID_DEC_LOW_D); else m_plate_pid->SetTunings(PLATE_PID_DEC_NORM_P, PLATE_PID_DEC_NORM_I, PLATE_PID_DEC_NORM_D); } } }
boolean Thermocycler::Pause() { if (iProgramState == ERunning && !iPaused) { iPauseTemp = GetPlateTemp(); iPaused = true; return true; } return false; }
void Thermocycler::ControlPeltier() { ThermalDirection newDirection = OFF; if (iProgramState == ERunning || (iProgramState == EComplete && ipCurrentStep != NULL)) { // Check whether we are nearing target and should switch to PID control if (iPlateControlMode == EBangBang && absf(iTargetPlateTemp - GetPlateTemp()) < PLATE_BANGBANG_THRESHOLD) { iPlateControlMode = EPIDPlate; iPlatePid.SetMode(AUTOMATIC); iPlatePid.ResetI(); } // Apply control mode if (iPlateControlMode == EBangBang) iPeltierPwm = iTargetPlateTemp > GetPlateTemp() ? MAX_PELTIER_PWM : MIN_PELTIER_PWM; iPlatePid.Compute(); if (iDecreasing && iTargetPlateTemp > PLATE_PID_DEC_LOW_THRESHOLD) { if (iTargetPlateTemp < GetPlateTemp()) iPlatePid.ResetI(); else iDecreasing = false; } if (iPeltierPwm > 0) newDirection = HEAT; else if (iPeltierPwm < 0) newDirection = COOL; else newDirection = OFF; } else { iPeltierPwm = 0; } iThermalDirection = newDirection; SetPeltier(newDirection, abs(iPeltierPwm)); }
void Thermocycler::ControlPeltier() { ThermalDirection newDirection = OFF; if (m_program_state == ERunning || (m_program_state == EComplete && m_current_step != NULL)) { // Check whether we are nearing target and should switch to PID control if (m_plate_control_mode == EBangBang && fabs(m_target_plate_temp - GetPlateTemp()) < PLATE_BANGBANG_THRESHOLD) { m_plate_control_mode = EPIDPlate; m_plate_pid->SetMode(AUTOMATIC); m_plate_pid->ResetI(); } // Apply control mode if (m_plate_control_mode == EBangBang) m_peltier_pwm = m_target_plate_temp > GetPlateTemp() ? MAX_PELTIER_PWM : MIN_PELTIER_PWM; m_plate_pid->Compute(); if (m_is_decreasing && m_target_plate_temp > PLATE_PID_DEC_LOW_THRESHOLD) { if (m_target_plate_temp < GetPlateTemp()) m_plate_pid->ResetI(); else m_is_decreasing = false; } if (m_peltier_pwm > 0) newDirection = HEAT; else if (m_peltier_pwm < 0) newDirection = COOL; else newDirection = OFF; } else { m_peltier_pwm = 0; } m_thermal_direction = newDirection; SetPeltier(newDirection, abs(m_peltier_pwm)); }
void Thermocycler::PrepareStep() { //update eta calc params if (ipPreviousStep == NULL || ipPreviousStep->GetTemp() != ipCurrentStep->GetTemp()) { iRamping = true; iRampElapsedTimeMs = 0; iRampStartTemp = GetPlateTemp(); } else { iCycleElapsedTimeMs = 0; //next step starts immediately } // Switch cycle to display ProgramComponent *pComp = ipProgram->GetComponent(ipProgram->GetCurrentComponentIndex()); if (pComp->GetType() == ProgramComponent::ECycle) { ipDisplayCycle = (Cycle*) pComp; } CalcPlateTarget(); SetPlateControlStrategy(); }
//private void Thermocycler::AdvanceToNextStep() { m_previous_step = m_current_step; m_current_step = m_program->GetNextStep(); if (m_current_step == NULL) return; //update eta calc params if (m_previous_step == NULL || m_previous_step->GetTemp() != m_current_step->GetTemp()) { m_is_ramping = true; m_ramp_start_time = millis(); m_ramp_start_temp = GetPlateTemp(); } else { m_cycle_start_time = millis(); //next step starts immediately } CalcPlateTarget(); SetPlateControlStrategy(); }
//private void Thermocycler::AdvanceToNextStep() { ipPreviousStep = ipCurrentStep; ipCurrentStep = ipProgram->GetNextStep(); if (ipCurrentStep == NULL) return; //update eta calc params if (ipPreviousStep == NULL || ipPreviousStep->GetTemp() != ipCurrentStep->GetTemp()) { iRamping = true; iRampStartTime = millis(); iRampStartTemp = GetPlateTemp(); } else { iCycleStartTime = millis(); //next step starts immediately } CalcPlateTarget(); SetPlateControlStrategy(); }
//PreprocessProgram initializes ETA parameters and validates/modifies ramp conditions void Thermocycler::PreprocessProgram() { Step* pCurrentStep; Step* pPreviousStep = NULL; iProgramHoldDurationS = 0; iEstimatedTimeRemainingS = 0; iHasCooled = false; iProgramControlledRampDurationS = 0; iProgramFastRampDegrees = 0; iElapsedFastRampDegrees = 0; iTotalElapsedFastRampDurationMs = 0; ipProgram->BeginIteration(); while ((pCurrentStep = ipProgram->GetNextStep()) && !pCurrentStep->IsFinal()) { //validate ramp if (pPreviousStep != NULL && pCurrentStep->GetRampDurationS() * 1000 < absf(pCurrentStep->GetTemp() - pPreviousStep->GetTemp()) * PLATE_FAST_RAMP_THRESHOLD_MS) { //cannot ramp that fast, ignored set ramp pCurrentStep->SetRampDurationS(0); } //update eta hold iProgramHoldDurationS += pCurrentStep->GetStepDurationS(); //update eta ramp if (pCurrentStep->GetRampDurationS() > 0) { //controlled ramp iProgramControlledRampDurationS += pCurrentStep->GetRampDurationS(); } else { //fast ramp double previousTemp = pPreviousStep ? pPreviousStep->GetTemp() : GetPlateTemp(); iProgramFastRampDegrees += absf(previousTemp - pCurrentStep->GetTemp()) - CYCLE_START_TOLERANCE; } pPreviousStep = pCurrentStep; } }
//PreprocessProgram initializes ETA parameters and validates/modifies ramp conditions void Thermocycler::PreprocessProgram() { Step* pCurrentStep; Step* pPreviousStep = NULL; m_program_hold_duration_sec = 0; m_estimated_time_remaining_sec = 0; m_has_cooled = false; m_program_controlled_ramp_duration_sec = 0; m_program_fast_ramp_degrees = 0; m_elapsed_fast_ramp_degrees = 0; m_total_elapsed_fast_ramp_duration_ms = 0; m_program->BeginIteration(); while ((pCurrentStep = m_program->GetNextStep()) && !pCurrentStep->IsFinal()) { //validate ramp if (pPreviousStep != NULL && pCurrentStep->GetRampDurationS() * 1000 < fabs(pCurrentStep->GetTemp() - pPreviousStep->GetTemp()) * PLATE_FAST_RAMP_THRESHOLD_MS) { //cannot ramp that fast, ignored set ramp pCurrentStep->SetRampDurationS(0); } //update eta hold m_program_hold_duration_sec += pCurrentStep->GetStepDurationS(); //update eta ramp if (pCurrentStep->GetRampDurationS() > 0) { //controlled ramp m_program_controlled_ramp_duration_sec += pCurrentStep->GetRampDurationS(); } else { //fast ramp double previousTemp = pPreviousStep ? pPreviousStep->GetTemp() : GetPlateTemp(); m_program_fast_ramp_degrees += fabs(previousTemp - pCurrentStep->GetTemp()) - CYCLE_START_TOLERANCE; } pPreviousStep = pCurrentStep; } }
void Thermocycler::SetPeltier(ThermalDirection dir, int pwm /* Signed value of peltier */) { // TODO Use table of internal heat & peltier efficiency if (dir == COOL) { if (GetPlateTemp() < 30) { pwm = pwm/8; } else if (GetPlateTemp() < 35) { pwm = pwm/4; } else if (GetPlateTemp() < 40) { pwm = pwm/2; } else if (GetPlateTemp() < 50) { pwm = pwm * 2 / 3; } } pwm = max(-MAX_PELTIER_PWM, min(MAX_PELTIER_PWM, (int)(pwm * iPowerOutputRatio))); Thermocycler::ThermalDirection dirActual; int pwmActual; if (dir != OFF && prevActualDirection != OFF && dir != prevActualDirection && prevActualPWMDuty!=0) { // Direction will be changed. if (prevPWMDuty==0 && pwm > PWM_SWITCHING_THRESHOLD) { pwmActual = pwm; dirActual = dir; } else { // Once set zero without switching relay pwmActual = 0; dirActual = prevActualDirection; } } else { // No need of switching direction. dirActual = dir; pwmActual = pwm; } PCR_DEBUG("Pout(A)="); PCR_DEBUG(pwmActual); PCR_DEBUG(", dir="); PCR_DEBUG_LINE(dirActual); #ifdef USE_FAN digitalWrite(PIN_FAN, PIN_FAN_VALUE_ON); #endif if (dirActual == COOL) { digitalWrite(PIN_WELL_INA, PIN_WELL_VALUE_OFF); digitalWrite(PIN_WELL_INB, PIN_WELL_VALUE_ON); } else if (dirActual == HEAT) { digitalWrite(PIN_WELL_INA, PIN_WELL_VALUE_ON); digitalWrite(PIN_WELL_INB, PIN_WELL_VALUE_OFF); } else { // Off digitalWrite(PIN_WELL_INA, PIN_WELL_VALUE_OFF); digitalWrite(PIN_WELL_INB, PIN_WELL_VALUE_OFF); } analogValuePeltier = pwmActual; int absOutput = (dir==COOL)?-pwmActual:pwmActual; #ifdef PIN_WELL_PWM_ACTIVE_LOW analogWrite(PIN_WELL_PWM, MAX_PELTIER_PWM-absOutput); #else analogWrite(PIN_WELL_PWM, absOutput); #endif /* PIN_WELL_PWM_ACTIVE_LOW */ statusBuff[statusIndex].wellOutput = pwm; prevDirection = dir; prevPWMDuty = pwm; prevActualDirection = dirActual; prevActualPWMDuty = pwmActual; }
// internal boolean Thermocycler::Loop() { ipCommunicator->Process(); unsigned long loopElapsedTimeMs = millis() - iPrevLoopStartTimeMs; iPrevLoopStartTimeMs = millis(); switch (iProgramState) { case EStartup: iTempUpdated = false; if (millis() > STARTUP_DELAY) { iProgramState = EStopped; iRestarted = false; if (!iRestarted && !ipCommunicator->CommandReceived()) { //check for stored program SCommand command; /* if (ProgramStore::RetrieveProgram(command, (char*)ipCommunicator->GetBuffer())) { ProcessCommand(command); } */ } } break; case ELidWait: if (GetLidTemp() >= iTargetLidTemp - LID_START_TOLERANCE) { //lid has warmed, begin program iThermalDirection = OFF; iPeltierPwm = 0; PreprocessProgram(); iProgramState = ERunning; ipProgram->BeginIteration(); AdvanceToNextStep(); iProgramStartTimeMs = millis(); } break; case ERunning: //update program if (!iPaused) { if (iRamping) { // Increment ramping time iRampElapsedTimeMs += loopElapsedTimeMs; } else { // Increment holding time iCycleElapsedTimeMs += loopElapsedTimeMs; } if (iProgramState == ERunning) { if (!ipCurrentStep->IsFinal() && (iNextStepPending || iNextCyclePending)) { if (iNextStepPending) { iNextStepPending = false; AdvanceToNextStep(); } if (iNextCyclePending) { iNextCyclePending = false; AdvanceToNextCycle(); } //check for program completion if (ipCurrentStep == NULL || ipCurrentStep->IsFinal()) { iProgramState = EComplete; } } else if (iRamping && abs(ipCurrentStep->GetTemp() - GetTemp()) <= CYCLE_START_TOLERANCE && GetRampElapsedTimeMs() > ipCurrentStep->GetRampDurationS() * 1000) { //begin step hold //eta updates if (ipCurrentStep->GetRampDurationS() == 0) { //fast ramp iElapsedFastRampDegrees += absf(GetTemp() - iRampStartTemp); iTotalElapsedFastRampDurationMs += iRampElapsedTimeMs; } if (iRampStartTemp > GetTemp()) { iHasCooled = true; } iRamping = false; iCycleElapsedTimeMs = 0; } else if (!iRamping && !ipCurrentStep->IsFinal() && iCycleElapsedTimeMs > (unsigned long)ipCurrentStep->GetStepDurationS() * 1000) { //begin next step AdvanceToNextStep(); //check for program completion if (ipCurrentStep == NULL || ipCurrentStep->IsFinal()) { iProgramState = EComplete; } } } break; case EComplete: PCR_DEBUG_LINE(ipCurrentStep->GetTemp()); if (iRamping && ipCurrentStep != NULL && abs(ipCurrentStep->GetTemp() - GetTemp()) <= CYCLE_START_TOLERANCE) { iRamping = false; } break; } } statusBuff[statusIndex].timestamp = millis(); //Read lid and well temp statusBuff[statusIndex].hardwareStatus = HARD_NO_ERROR; HardwareStatus result = iPlateThermistor.ReadTemp(); if (result!=HARD_NO_ERROR) { statusBuff[statusIndex].hardwareStatus = result; } result = iLidThermistor.ReadTemp(); if (result!=HARD_NO_ERROR) { statusBuff[statusIndex].hardwareStatus = result; } statusBuff[statusIndex].lidTemp = GetLidTemp(); statusBuff[statusIndex].wellTemp = GetPlateTemp(); float lidTemp = 0; float wellTemp = 0; CheckHardware(&lidTemp, &wellTemp); PCR_DEBUG("L="); PCR_DEBUG(lidTemp); PCR_DEBUG(" W=wellTemp"); PCR_DEBUG_LINE(wellTemp); iLidThermistor.setTemp(lidTemp); iPlateThermistor.setTemp(wellTemp); double estimatedAirTemp = wellTemp * 0.4 + lidTemp * 0.6; // Estimated delta to next 1 sec double diff = ((wellTemp - iEstimatedSampleTemp)/THETA_WELL + (estimatedAirTemp-iEstimatedSampleTemp)/THETA_LID ) / CAPACITY_TUBE; if (!iTempUpdated) { iTempUpdated = true; iEstimatedSampleTemp = estimatedAirTemp; } else if ( 5>diff && diff > -5) { iEstimatedSampleTemp += diff; } CalcPlateTarget(); // Check error //if (iHardwareStatus==HARD_NO_ERROR || true) { //TODO WELL_TEST (dummy line) if (iHardwareStatus==HARD_NO_ERROR) { //TODO WELL_TEST ControlLid(); ControlPeltier(); if (iHardwareStatus!=HARD_NO_ERROR) { PCR_DEBUG("ERR="); PCR_DEBUG_LINE(iHardwareStatus); } } else { PCR_DEBUG_LINE("ALL OFF"); iProgramState = EError; SetPeltier(OFF, 0); SetLidOutput(0); } //program UpdateEta(); #ifdef USE_LCD ipDisplay->Update(); #endif statusIndex = (statusIndex+1) % CyclerStatusBuffSize; statusCount++; return true; }
// internal void Thermocycler::Loop() { digitalWrite(6, (lamp)?HIGH:LOW); digitalWrite(5, (!lamp)?HIGH:LOW); lamp = !lamp; switch (iProgramState) { case EStartup: if (millis() > STARTUP_DELAY) { iProgramState = EStopped; iRestarted = false; if (!iRestarted && !ipSerialControl->CommandReceived()) { //check for stored program SCommand command; if (ProgramStore::RetrieveProgram(command, (char*)ipSerialControl->GetBuffer())) ProcessCommand(command); } } break; case ELidWait: if (GetLidTemp() >= iTargetLidTemp - LID_START_TOLERANCE) { //lid has warmed, begin program iThermalDirection = OFF; iPeltierPwm = 0; PreprocessProgram(); iProgramState = ERunning; ipProgram->BeginIteration(); AdvanceToNextStep(); iProgramStartTimeMs = millis(); } break; case ERunning: //update program if (iProgramState == ERunning) { if (iRamping && abs(ipCurrentStep->GetTemp() - GetPlateTemp()) <= CYCLE_START_TOLERANCE && GetRampElapsedTimeMs() > ipCurrentStep->GetRampDurationS() * 1000) { //begin step hold //eta updates if (ipCurrentStep->GetRampDurationS() == 0) { //fast ramp iElapsedFastRampDegrees += absf(GetPlateTemp() - iRampStartTemp); iTotalElapsedFastRampDurationMs += millis() - iRampStartTime; } if (iRampStartTemp > GetPlateTemp()) { iHasCooled = true; } iRamping = false; iCycleStartTime = millis(); } else if (!iRamping && !ipCurrentStep->IsFinal() && millis() - iCycleStartTime > (unsigned long)ipCurrentStep->GetStepDurationS() * 1000) { //begin next step AdvanceToNextStep(); //check for program completion if (ipCurrentStep == NULL || ipCurrentStep->IsFinal()) { iProgramState = EComplete; } } } break; case EComplete: if (iRamping && ipCurrentStep != NULL && abs(ipCurrentStep->GetTemp() - GetPlateTemp()) <= CYCLE_START_TOLERANCE) iRamping = false; break; } //lid iLidThermistor.ReadTemp(); ControlLid(); //plate iPlateThermistor.ReadTemp(); CalcPlateTarget(); ControlPeltier(); //program UpdateEta(); ipDisplay->Update(); ipSerialControl->Process(); }
void Thermocycler::Loop() { switch (m_program_state) { case EStartup: if (millis() > STARTUP_DELAY) { m_program_state = EStopped; //if (!m_is_restarted && !m_serial_control->CommandReceived()) { //check for stored program //SCommand command; //if (ProgramStore::RetrieveProgram(command, (char*)m_serial_control->GetBuffer())) // ProcessCommand(command); } } break; case ELidWait: if (GetLidTemp() >= m_target_lid_temp - LID_START_TOLERANCE) { //lid has warmed, begin program m_thermal_direction = OFF; m_peltier_pwm = 0; PreprocessProgram(); m_program_state = ERunning; m_program->BeginIteration(); AdvanceToNextStep(); m_program_start_time_ms = millis(); } break; case ERunning: //update program if (m_program_state == ERunning) { if (m_is_ramping && abs(m_current_step->GetTemp() - GetPlateTemp()) <= CYCLE_START_TOLERANCE && GetRampElapsedTimeMs() > m_current_step->GetRampDurationS() * 1000) { //begin step hold //eta updates if (m_current_step->GetRampDurationS() == 0) { //fast ramp m_elapsed_fast_ramp_degrees += fabs(GetPlateTemp() - m_ramp_start_temp); m_total_elapsed_fast_ramp_duration_ms += millis() - m_ramp_start_time; } if (m_ramp_start_temp > GetPlateTemp()) m_has_cooled = true; m_is_ramping = false; m_cycle_start_time = millis(); } else if (!m_is_ramping && !m_current_step->IsFinal() && millis() - m_cycle_start_time > (unsigned long)m_current_step->GetStepDurationS() * 1000) { //begin next step AdvanceToNextStep(); //check for program completion if (m_current_step == NULL || m_current_step->IsFinal()) m_program_state = EComplete; } } break; case EComplete: if (m_is_ramping && m_current_step != NULL && abs(m_current_step->GetTemp() - GetPlateTemp()) <= CYCLE_START_TOLERANCE) m_is_ramping = false; break; case EStopped: //Nothing case EError: //Nothing case EClear: //Nothing break; } //lid m_lid_thermistor.ReadTemp(); ControlLid(); //plate m_plate_thermistor.ReadTemp(); CalcPlateTarget(); ControlPeltier(); //program UpdateEta(); m_display->Update(); m_serial_control->Process(); }