Andrea Faustinelli / espresso-for-geeks-master
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers brewcontrol.cpp Source File

brewcontrol.cpp

00001 /* Copyright (c) 2017 Philippe Kalaf, MIT License
00002  *
00003  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
00004  * and associated documentation files (the "Software"), to deal in the Software without restriction, 
00005  * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
00006  * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
00007  * furnished to do so, subject to the following conditions:
00008  *
00009  * The above copyright notice and this permission notice shall be included in all copies or 
00010  * substantial portions of the Software.
00011  *
00012  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
00013  * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
00014  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
00015  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
00016  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00017  */
00018 
00019 
00020 // brew worker period in ms 
00021 #define BREW_WORKER_PERIOD 100
00022 
00023 // Boiler PID worker in ms
00024 #define PID_WORKER_PERIOD 500
00025 
00026 // PWM period
00027 // 0.8333 for 60Hz, 1 for 50Hz for 1% resolution
00028 #define BOILER_PWM_PERIOD 0.8333
00029 
00030 // Soft stop time
00031 #define SOFT_STOP_TIME_S 7.0
00032 
00033 // Steam timeout in seconds
00034 #define STEAM_TIMEOUT 300.0
00035 
00036 // Steam temperature
00037 #define STEAM_TEMPERATURE 140.0
00038 
00039 // Manage different brew modes and timings
00040 #include "brewcontrol.h"
00041 
00042 BrewControl::BrewControl(   PinName brew_pin, 
00043                             PinName flow_sensor_pin,
00044                             PinName zcd_input_pin,
00045                             PinName pump_control_pin,
00046                             PinName pressure_sensor_pin,
00047                             PinName temp_sensor_pin,
00048                 PinName temp2_sensor_pin,
00049                             PinName boiler_pwm_pin
00050                             ) : 
00051                             _brew_switch(brew_pin, 0), 
00052                             _flow_sensor(flow_sensor_pin), 
00053                             _pump_control(zcd_input_pin, pump_control_pin),
00054                             _pressure_sensor(pressure_sensor_pin),
00055                             _temp_sensor(temp_sensor_pin),
00056                 _temp2_sensor(temp2_sensor_pin),
00057                             _boiler_pwm(boiler_pwm_pin),
00058                             _brew_worker_thread(osPriorityNormal, 768),
00059                             _pid_worker_thread(osPriorityNormal, 256)
00060 {
00061     _preinfuse_time = 0;
00062     _brew_switch = 0;
00063 
00064     // at 60Hz, we got 120 zero-crosses per sec, we want to capture 100 of 
00065     // those within each PWM period
00066     _boiler_pwm.period(BOILER_PWM_PERIOD);
00067     
00068     // let's start at 93 C
00069     _target_shot_temperature = 93;
00070 
00071     // 9 bars is default
00072     _target_shot_pressure = 9;
00073 
00074     // this is used for steam mode to return to prev target temp when done
00075     _prev_temp = 0;
00076 
00077     _boiler_pid.setPIDGains(0.075, 0.1, 0.9);
00078     _boiler_pid.setIntegratorLimits(0, 1);
00079 
00080     // Boiler is on by default
00081     enable_boiler();
00082 
00083     // Start main worker thread of brewing
00084     _brew_worker_thread.start(callback(this, &BrewControl::_brew_worker));
00085 
00086     // Start pid worker thread
00087     _pid_worker_thread.start(callback(this, &BrewControl::_boiler_pid_worker));
00088 
00089     _set_state(STOPPED);
00090 }
00091 
00092 float BrewControl::get_current_temperature_side()
00093 {
00094     return _temp_sensor.read();
00095 }
00096 
00097 float BrewControl::get_current_temperature_top()
00098 {
00099     return _temp2_sensor.read();
00100 }
00101 
00102 float BrewControl::get_current_temperature()
00103 {
00104     // Let's return average of both sensors
00105     //return (_temp_sensor.read() + _temp2_sensor.read())/2;
00106     return _temp_sensor.read();
00107 }
00108 
00109 void BrewControl::set_shot_temperature(float shot_temp)
00110 {
00111     _brew_worker_mutex.lock();
00112 
00113     if (shot_temp <= 20)
00114         _target_shot_temperature = 20;
00115     else if (shot_temp >= 150)
00116         _target_shot_temperature = 150;
00117     else
00118         _target_shot_temperature = shot_temp;
00119 
00120     _brew_worker_mutex.unlock();
00121 }
00122 
00123 float BrewControl::get_shot_temperature()
00124 {
00125     return _target_shot_temperature;
00126 }
00127 
00128 void BrewControl::set_shot_pressure(float pressure)
00129 {
00130     _brew_worker_mutex.lock();
00131 
00132     if (pressure <= 0)
00133         _target_shot_pressure = 0;
00134     else if (pressure >= 12)
00135         _target_shot_pressure = 12;
00136     else
00137         _target_shot_pressure = pressure;
00138 
00139     _brew_worker_mutex.unlock();
00140 }
00141 
00142 // Return pressure in bars
00143 float BrewControl::get_current_pressure()
00144 {
00145     return _pressure_sensor.read_bars();
00146 }
00147 
00148 int BrewControl::get_shot_volume()
00149 {
00150     return _target_shot_volume;
00151 }
00152 
00153 void BrewControl::_boiler_pid_worker()
00154 {
00155     while(true)
00156     {
00157         ThisThread::sleep_for(PID_WORKER_PERIOD);
00158 
00159         if(!_enable_boiler)
00160             continue;
00161 
00162         // take temperature measurement
00163         float latestTemp = get_current_temperature(); 
00164 
00165         float power = 0.0;
00166 
00167         // if the temperature is near zero, we assume there's an error
00168         if ( latestTemp > 0.5 ) {
00169             // calculate PID update
00170             power = _boiler_pid.update(_target_shot_temperature - latestTemp,
00171                     latestTemp);
00172         }
00173 
00174         // Validate number
00175         if ( power > 1 )
00176             power = 1;
00177         else if ( power < 0 )
00178             power = 0;
00179 
00180         // let's set new power on boiler
00181         _boiler_pwm = power;
00182 
00183         // store the latest temperature reading
00184         _latest_temp = latestTemp;
00185     }
00186 }
00187 
00188 void BrewControl::enable_boiler()
00189 {
00190     _enable_boiler = 1;
00191 }
00192 
00193 void BrewControl::disable_boiler()
00194 {
00195     _enable_boiler = 0;
00196     _boiler_pwm = 0;
00197 }
00198 
00199 bool BrewControl::toggle_boiler()
00200 {
00201     if (_enable_boiler)
00202         disable_boiler();
00203     else
00204         enable_boiler();
00205     
00206     return _enable_boiler;
00207 }
00208 
00209 void BrewControl::pressure_up(uint8_t value)
00210 {
00211     // limit to 12 bars
00212     if (get_current_pressure() <= 11.5)
00213         _pump_control.level_up(value);
00214 }
00215 
00216 void BrewControl::pressure_down(uint8_t value)
00217 {
00218     _pump_control.level_down(value);
00219 }
00220 
00221 // pre-infuse time in seconds
00222 // set to 0 to disable
00223 void BrewControl::set_preinfuse_time(int time)
00224 {
00225     _brew_worker_mutex.lock();
00226 
00227     if (time > 0)
00228         _preinfuse_time = time;
00229     else
00230         _preinfuse_time = 0;
00231 
00232     _brew_worker_mutex.unlock();
00233 }
00234 
00235 int BrewControl::get_preinfuse_time()
00236 {
00237     return _preinfuse_time;
00238 }
00239 
00240 void BrewControl::stop_preinfuse_now()
00241 {
00242     _stop_preinfuse = 1;
00243 }
00244 
00245 uint8_t BrewControl::get_pump_level()
00246 {
00247     return _pump_control.get_level();
00248 }
00249 
00250 // This is to set the wanted shot time
00251 void BrewControl::set_shot_time(int time)
00252 {
00253     _brew_worker_mutex.lock();
00254 
00255     _target_shot_time = time;
00256 
00257     _brew_worker_mutex.unlock();
00258 }
00259 
00260 // This is to set the wanted shot volume
00261 void BrewControl::set_shot_volume(int volume)
00262 {
00263     _brew_worker_mutex.lock();
00264 
00265     if (volume >= 0)
00266         _target_shot_volume = volume;
00267 
00268     _brew_worker_mutex.unlock();
00269 }
00270 
00271 // This is to set the wanted flow rate (ml/s)
00272 void BrewControl::set_shot_flow_rate(float flow_rate)
00273 {
00274     _brew_worker_mutex.lock();
00275 
00276     _target_flow_rate = flow_rate;
00277 
00278     _brew_worker_mutex.unlock();
00279 }
00280 
00281 // return current shot_clock in seconds
00282 float BrewControl::get_current_time()
00283 {
00284     return _shot_clock;
00285 }
00286 
00287 // return current volume in ml
00288 float BrewControl::get_current_volume()
00289 {
00290     return _flow_sensor.get_volume();
00291 }
00292 
00293 // return the current flow rate
00294 float BrewControl::get_current_flow_rate()
00295 {
00296     return _flow_sensor.get_flow_rate();
00297 }
00298 
00299 // read brew on/off state
00300 uint8_t BrewControl::get_state()
00301 {
00302     return _state;
00303 }
00304 
00305 // Internal helper function to set brew states properly
00306 void BrewControl::_set_state(uint8_t new_state)
00307 {
00308     // Let's start the average pressure calculation when we start brewing
00309     if ((_state == STOPPED || _state == PRE_INFUSING) && new_state == BREWING)
00310         _pressure_sensor.start_count();
00311     // Let's stop the average pressure calculation when we stop brewing
00312     else if (_state == BREWING && (new_state == SOFT_STOPPING || new_state == STOPPED))
00313         _average_pressure = _pressure_sensor.stop_count();
00314 
00315     _state = new_state;
00316     if (_state == BREWING || _state == STEAMING || _state == PRE_INFUSING)
00317         _brew_worker_thread.flags_set(1);
00318 }
00319 
00320 void BrewControl::_brew_worker()
00321 {
00322     while(true)
00323     {
00324         // If we are not brewing, let's just wait until we start brewing
00325         if (_state != PRE_INFUSING && _state != BREWING && _state != STEAMING)
00326         {
00327             // _set_state() will set the flag when we start brewing
00328             ThisThread::flags_wait_any(1);
00329         }
00330         
00331         _brew_worker_mutex.lock();
00332 
00333 #ifdef LOG
00334         _log_brew_params();
00335 #endif
00336 
00337         if (_state == PRE_INFUSING)
00338         {
00339             // First let's fill up the portafilter
00340             if (_flow_sensor.get_volume() <= PORTAFILTER_VOLUME && !_stop_preinfuse)
00341             {
00342                 _brew_worker_mutex.unlock();
00343                 continue;
00344             }
00345 
00346             // It's full, let's stop the pump for set pre-infuse time
00347             if (_pump_control.get_level() != 0)
00348             {
00349                 _pump_control.set_level(0);
00350                 _shot_clock.reset();
00351             }
00352 
00353             // Once pre-infuse time runs out, set brew mode
00354             if (_shot_clock.read() >= _preinfuse_time)
00355             {
00356                 _mode = _prev_mode;
00357                 _pump_control.set_level(60);
00358                 _flow_sensor.reset_count();
00359                 _shot_clock.reset();
00360                 _set_state(BREWING);
00361                 _stop_preinfuse = 0;
00362             }
00363 
00364             _brew_worker_mutex.unlock();
00365             continue;
00366         }
00367 
00368         if(_mode == MODE_TIME)
00369         {
00370             // Auto-adjust pressure to target
00371             float error = _target_shot_pressure - get_current_pressure();
00372             if(error < -0.25)
00373                 pressure_down();
00374             else if(error > 0.25) 
00375                 pressure_up();
00376 
00377             if(_shot_clock.read() >= _target_shot_time)
00378             {
00379                 _brew_worker_mutex.unlock();
00380                 soft_stop();
00381                 _brew_worker_mutex.lock();
00382             }
00383         }
00384         else if(_mode == MODE_YIELD)
00385         {
00386             // Auto-adjust pressure to target
00387             float error = _target_shot_pressure - get_current_pressure();
00388             if(error < -0.25)
00389                 pressure_down();
00390             else if(error > 0.25)
00391                 pressure_up();
00392 
00393             if(_flow_sensor.get_volume() >= _target_shot_volume)
00394             {
00395                 _brew_worker_mutex.unlock();
00396                 soft_stop();
00397                 _brew_worker_mutex.lock();
00398             }
00399         }
00400         else if(_mode == MODE_TIME_YIELD)
00401         {
00402             // Re-calculate target flowrate =
00403             // remaining volume / remaining time
00404             _target_flow_rate = (_target_shot_volume - _flow_sensor.get_volume())
00405                 / (_target_shot_time - _shot_clock.read());
00406             // Auto-adjust flow-rate
00407             if(_target_flow_rate < 0)
00408                 // oops! we have run out of time! go full power!
00409                 _pump_control.set_level(100);
00410             else if(_target_flow_rate - _flow_sensor.get_flow_rate() < 0)
00411                 pressure_down();
00412             else
00413                 pressure_up();
00414 
00415             // Stop when target shot volume is reached
00416             if(_flow_sensor.get_volume() >= _target_shot_volume)
00417             {
00418                 _brew_worker_mutex.unlock();
00419                 soft_stop();
00420                 _brew_worker_mutex.lock();
00421             }
00422         }
00423         else if(_mode == MODE_MANUAL)
00424         {
00425         }
00426         else if(_mode == MODE_STEAM)
00427         {
00428             // automatically stop steaming after STEAM_TIMEOUT
00429             if (_shot_clock.read() >= STEAM_TIMEOUT)
00430                 _stop();
00431         }
00432 
00433         ThisThread::sleep_for(BREW_WORKER_PERIOD);
00434         _brew_worker_mutex.unlock();
00435 
00436     }
00437 }
00438 
00439 uint8_t BrewControl::start(uint8_t mode)
00440 {
00441     _brew_worker_mutex.lock();
00442 
00443     // We are already brewing, return brewing mode
00444     if(_state == BREWING) 
00445     {
00446         _brew_worker_mutex.unlock();
00447         return _mode;
00448     }
00449     
00450     _mode = mode;
00451 
00452     // reset shot clock and flow sensor
00453     _flow_sensor.reset_count();
00454     _shot_clock.reset();
00455     _shot_clock.start();
00456 
00457     // Let's save settings before we set pre-infuse mode
00458     // only pre-infuse if set to > 0s and not in manual or steam mode
00459     if (_preinfuse_time && _mode != MODE_MANUAL && _mode != MODE_STEAM)
00460     {
00461         _prev_mode = _mode;
00462     // set pre-infuse mode
00463     _set_state(PRE_INFUSING);
00464 
00465     // we pre-infuse at low pressure
00466         _pump_control.set_level(60);
00467     }
00468     else
00469         _set_state(BREWING);
00470 
00471     if(_mode == MODE_STEAM)
00472     {
00473     // save currently set temperature to return to it after steaming
00474     _prev_temp = get_shot_temperature();
00475     set_shot_temperature(STEAM_TEMPERATURE);
00476     }
00477     else
00478     // Open solenoid
00479     _brew_switch = 1;
00480     
00481     _brew_worker_mutex.unlock();
00482     
00483     return _mode;
00484 }
00485 
00486 void BrewControl::toggle_solenoid()
00487 {
00488     _brew_worker_mutex.lock();
00489 
00490     if(_brew_switch)
00491     _brew_switch = 0;
00492     else
00493     _brew_switch = 1;
00494 
00495     _brew_worker_mutex.unlock();
00496 }
00497 
00498 // This function is sometimes called from an ISR
00499 void BrewControl::_stop()
00500 {
00501     _set_state(STOPPED);
00502     
00503     // Close solenoid
00504     _brew_switch = 0;
00505 
00506     // Stop and reset all counters and brew params
00507     _shot_clock.stop();
00508     _shot_clock.reset();
00509     _target_shot_volume = 0;
00510     _target_shot_time = 0;
00511     _flow_sensor.reset_count();
00512 
00513     // if we were in steam mode, _prev_temp will have been set
00514     if(_prev_temp)
00515     {
00516         _target_shot_temperature = _prev_temp;
00517     _prev_temp = 0;
00518     }
00519 }
00520 
00521 void BrewControl::soft_stop()
00522 {
00523     _brew_worker_mutex.lock();
00524 
00525     // Stop immediately if in steam mode
00526     if (_mode == MODE_STEAM)
00527     {
00528     _stop();
00529         _brew_worker_mutex.unlock();
00530     return;
00531     }
00532 
00533     // Turn off pump
00534     _pump_control.set_level(0);
00535 
00536     // shot clock
00537     _shot_clock.stop();
00538 
00539     _set_state(SOFT_STOPPING);
00540 
00541     // Call stop() after SOFT_STOP_TIME_S
00542     _soft_stop_timer.attach(callback(this, &BrewControl::_stop),
00543             SOFT_STOP_TIME_S);
00544 
00545     _brew_worker_mutex.unlock();
00546 }
00547 
00548 float BrewControl::get_average_pressure()
00549 {
00550     return _average_pressure;
00551 }
00552 
00553 uint8_t BrewControl::toggle(uint8_t mode)
00554 {
00555     if(_state == BREWING || _state == PRE_INFUSING)
00556     soft_stop();
00557     else if(_state == STOPPED)
00558     start(mode);
00559 
00560     return _state;
00561 }
00562 
00563 PhaseControl *BrewControl::get_pump_control_ptr()
00564 {
00565     return &_pump_control;
00566 }
00567 
00568 uint16_t BrewControl::get_last_pulse_count_side()
00569 {
00570     return _temp_sensor.get_last_pulse_count();
00571 }
00572 
00573 uint16_t BrewControl::get_last_pulse_count_top()
00574 {
00575     return _temp2_sensor.get_last_pulse_count();
00576 }