void Thermocycler::ControlPeltier() { Thermocycler::ThermalDirection newDirection = Thermocycler::ThermalDirection::OFF; if (iProgramState == ERunning || (iProgramState == EComplete && ipCurrentStep != NULL)) { // Check whether we are nearing target and should switch to PID control // float plateTemp = GetPlateTemp(); /* Test: Use estimated sample temp instead or measured well temp */ float plateTemp = GetTemp(); if (iPlateControlMode == EBangBang && absf(iTargetPlateTemp - plateTemp) < PLATE_BANGBANG_THRESHOLD) { iPlateControlMode = EPIDPlate; iPlatePid.SetMode(AUTOMATIC); iPlatePid.ResetI(); } // Apply control mode if (iPlateControlMode == EBangBang) { // Full drive iPeltierPwm = (iTargetPlateTemp > plateTemp) ? MAX_PELTIER_PWM : MIN_PELTIER_PWM; } iPlatePid.Compute(); if (iDecreasing && iTargetPlateTemp > PLATE_PID_DEC_LOW_THRESHOLD) { if (iTargetPlateTemp < plateTemp) { 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, iPeltierPwm); }
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)); }
// 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; }