Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
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 }
Generated on Thu Jul 14 2022 00:36:34 by
1.7.2