Sun, 24 Nov 2019 16:44:00 +0100
Version 0.3.7. The WiFi task uses the new event handlers. Cooling temperature top is now 45 instead of 30 degreees for pitching Kveik. One extra cosmetic message during OTA update.
0 | 1 | /** |
2 | * @file task_driver.c | |
3 | * @brief BrewBoard relays driver. Control the hardware outputs with the | |
4 | * Solid State relays for the Mash/Boil kettle (MLT, the Hot | |
5 | * Liquer Tank (HLT) and the pump. The MLT has a PID controller | |
6 | * during mashing, and a simple bang on/off control during the | |
7 | * boil. | |
8 | * The HLT output can be off, bang on/off, or just on if the MLT | |
9 | * is off, depending on the configuration. | |
10 | * Use SSR modules that switch during zero crossing of the mains | |
11 | * power, so that when one is turned on and on the same time the | |
12 | * other is turned off, they won't be active at the same time. | |
13 | */ | |
14 | ||
15 | #include "config.h" | |
16 | ||
17 | ||
1
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
18 | #define SSR_MLT CONFIG_SSR_MLT_GPIO ///< GPIO SSR MLT pin |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
19 | #define SSR_HLT CONFIG_SSR_HLT_GPIO ///< GPIO SSR HLT pin |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
20 | #define SSR_PUMP CONFIG_SSR_PUMP_GPIO ///< GPIO Pump relay pin |
0 | 21 | |
22 | ||
1
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
23 | bool outEnable = false; ///< Enable outputs flag |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
24 | DRIVER_State * driver_state; ///< Driver state |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
25 | SemaphoreHandle_t xSemaphoreDriver = NULL; ///< Driver state lock |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
26 | int MLT_pin = 0; ///< MLT state |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
27 | int HLT_pin = 0; ///< HLT state |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
28 | int Pump_pin = 0; ///< Pump state |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
29 | double Input = 0; ///< PID input value |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
30 | double Output = 0; ///< PID output value |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
31 | double Setpoint = 0; ///< PID setpoint value |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
32 | int MLT_Mode = MLT_MODE_NONE; ///< MLT mode flag |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
33 | double HLT_Input = 0; ///< HLT input value |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
34 | double HLT_Setpoint = 0; ///< HLT setpoint values |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
35 | int HLT_Output = 0; ///< HLT output value |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
36 | int HLT_Mode = HLT_MODE_NONE; ///< HLT mode flag |
0 | 37 | |
38 | static const char *TAG = "task_driver"; | |
39 | ||
40 | extern SemaphoreHandle_t xSemaphoreDS18B20; | |
41 | extern DS18B20_State * ds18b20_state; | |
42 | extern unsigned long lastTime; | |
43 | ||
44 | ||
45 | /** | |
46 | * @brief Turn the MLT SSR on or off. | |
47 | */ | |
48 | void MLT(int onoff); | |
49 | ||
50 | /** | |
51 | * @brief Turn the HLT SSR on or off. | |
52 | */ | |
53 | void HLT(int onoff); | |
54 | ||
55 | /** | |
56 | * @brief Turn the Pump on or off. | |
57 | */ | |
58 | void Pump(int onoff); | |
59 | ||
60 | ||
61 | ||
62 | void MLT(int onoff) { | |
63 | ||
64 | if (onoff && outEnable) { | |
65 | gpio_set_level(SSR_MLT, 1); | |
66 | MLT_pin = 1; | |
67 | } else { | |
68 | gpio_set_level(SSR_MLT, 0); | |
69 | MLT_pin = 0; | |
70 | } | |
71 | } | |
72 | ||
73 | ||
74 | ||
75 | void HLT(int onoff) { | |
76 | ||
77 | if (onoff && outEnable) { | |
78 | gpio_set_level(SSR_HLT, 1); | |
79 | HLT_pin = 1; | |
80 | } else { | |
81 | gpio_set_level(SSR_HLT, 0); | |
82 | HLT_pin = 0; | |
83 | } | |
84 | } | |
85 | ||
86 | ||
87 | ||
88 | void Pump(int onoff) { | |
89 | ||
90 | if (onoff && outEnable) { | |
91 | gpio_set_level(SSR_PUMP, 1); | |
92 | Pump_pin = 1; | |
93 | } else { | |
94 | gpio_set_level(SSR_PUMP, 0); | |
95 | Pump_pin = 0; | |
96 | } | |
97 | } | |
98 | ||
99 | ||
100 | ||
1
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
101 | /** |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
102 | * @brief Load PID settings from equipment record. |
ad2c8b13eb88
Updated lots of doxygen comments
Michiel Broek <mbroek@mbse.eu>
parents:
0
diff
changeset
|
103 | */ |
0 | 104 | void LoadPIDsettings() { |
105 | PID_SetTunings(equipment.PID_kP, equipment.PID_kI, equipment.PID_kD, equipment.PID_POn); | |
106 | PID_SetSampleTime(equipment.SampleTime); | |
107 | ||
108 | /* | |
109 | * Initialize the PID | |
110 | */ | |
111 | Output = 0.0; // Reset internal Iterm. | |
112 | PID_SetMode(PID_MANUAL); | |
113 | PID_SetMode(PID_AUTOMATIC); | |
114 | } | |
115 | ||
116 | ||
117 | ||
118 | void task_driver(void *pvParameter) | |
119 | { | |
120 | TickType_t wait_ticks, last_tick, now_tick; | |
121 | bool rc; | |
122 | unsigned long now, RealTime, w_StartTime = 0; | |
123 | ||
124 | ESP_LOGI(TAG, "Starting output drivers"); | |
125 | ||
126 | /* | |
127 | * Configure IOMUX register. | |
128 | */ | |
129 | gpio_pad_select_gpio(SSR_MLT); | |
130 | gpio_set_direction(SSR_MLT, GPIO_MODE_OUTPUT); | |
131 | gpio_pad_select_gpio(SSR_HLT); | |
132 | gpio_set_direction(SSR_HLT, GPIO_MODE_OUTPUT); | |
133 | gpio_pad_select_gpio(SSR_PUMP); | |
134 | gpio_set_direction(SSR_PUMP, GPIO_MODE_OUTPUT); | |
135 | ||
136 | /* | |
137 | * Initialize state | |
138 | */ | |
139 | driver_state = malloc(sizeof(DRIVER_State)); | |
140 | driver_state->enable = outEnable = false; | |
141 | driver_state->mlt_gpio = SSR_MLT; | |
142 | driver_state->mlt_mode = MLT_MODE_NONE; | |
143 | driver_state->mlt_sp = driver_state->mlt_pv = 0.0; | |
144 | driver_state->mlt_power = 0; | |
145 | driver_state->hlt_gpio = SSR_HLT; | |
146 | driver_state->hlt_mode = HLT_MODE_NONE; | |
147 | driver_state->hlt_sp = driver_state->hlt_pv = 0.0; | |
148 | driver_state->hlt_power = 0; | |
149 | driver_state->hlt_and_mlt = false; | |
150 | driver_state->pump_gpio = SSR_PUMP; | |
151 | driver_state->pump_run = 0; | |
152 | ||
153 | PID(&Input, &Output, &Setpoint, 150, 1.5, 15000, PID_P_ON_E, PID_DIRECT); | |
154 | ||
155 | /* | |
156 | * One loop must complete in 20 mSecs, that is one mains | |
157 | * frequency period cycle in 50 Hz countries. | |
158 | */ | |
159 | while (1) { | |
160 | ||
161 | last_tick = xTaskGetTickCount(); | |
162 | ||
163 | if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { | |
164 | /* | |
165 | * Get the current temperature readings | |
166 | */ | |
167 | if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { | |
168 | if (ds18b20_state->mlt_valid) | |
169 | driver_state->mlt_pv = ds18b20_state->mlt_temperature; | |
170 | if (ds18b20_state->hlt_valid) | |
171 | driver_state->hlt_pv = ds18b20_state->hlt_temperature; | |
172 | xSemaphoreGive(xSemaphoreDS18B20); | |
173 | } | |
174 | ||
175 | /* | |
176 | * Other values that we need | |
177 | */ | |
178 | Input = driver_state->mlt_pv; | |
179 | Setpoint = driver_state->mlt_sp; | |
180 | if (driver_state->mlt_mode != MLT_Mode) { | |
181 | if (driver_state->mlt_mode == MLT_MODE_BANG) { | |
182 | PID_SetMode(PID_MANUAL); | |
183 | } else if (driver_state->mlt_mode == MLT_MODE_PID) { | |
184 | LoadPIDsettings(); | |
185 | } | |
186 | MLT_Mode = driver_state->mlt_mode; | |
187 | ESP_LOGI(TAG, "MLT mode set to %d", MLT_Mode); | |
188 | } | |
189 | if (driver_state->hlt_mode != HLT_Mode) { | |
190 | HLT_Mode = driver_state->hlt_mode; | |
191 | ESP_LOGI(TAG, "HLT mode set to %d", HLT_Mode); | |
192 | } | |
193 | outEnable = driver_state->enable; | |
194 | HLT_Input = driver_state->hlt_pv; | |
195 | HLT_Setpoint = driver_state->hlt_sp; | |
196 | xSemaphoreGive(xSemaphoreDriver); | |
197 | } | |
198 | ||
199 | rc = false; | |
200 | now = xTaskGetTickCount() * portTICK_PERIOD_MS; | |
201 | ||
202 | if ((PID_GetMode() == PID_AUTOMATIC) && (MLT_Mode == MLT_MODE_PID)) { | |
203 | rc = PID_Compute(); | |
204 | RealTime = (equipment.SampleTime * equipment.MashPower) / 100; | |
205 | } else { | |
206 | /* | |
207 | * Schedule the loop ourself. | |
208 | */ | |
209 | unsigned long timeChange = (now - lastTime); | |
210 | if (timeChange >= equipment.SampleTime) { | |
211 | lastTime = now; | |
212 | rc = true; | |
213 | } | |
214 | RealTime = equipment.SampleTime; | |
215 | if (driver_state->mlt_mode == MLT_MODE_BANG) { | |
216 | Output = (Input < Setpoint) ? 255:0; | |
217 | } | |
218 | if (driver_state->mlt_mode == MLT_MODE_NONE || driver_state->mlt_mode == MLT_MODE_OFF) { | |
219 | Output = 0; | |
220 | } | |
221 | } | |
222 | ||
223 | if (rc) { | |
224 | w_StartTime = now; | |
225 | } | |
226 | ||
227 | if ((int)((Output / 255.0) * RealTime) > (now - w_StartTime)) { | |
228 | MLT(1); | |
229 | if ((equipment.SSR2 == SSR2_HLT_SHARE) || (equipment.SSR2 == SSR2_ON_IDLE)) { | |
230 | HLT(0); | |
231 | HLT_Output = 0; | |
232 | } | |
233 | } else { | |
234 | MLT(0); | |
235 | if (equipment.SSR2 == SSR2_HLT_SHARE) { | |
236 | if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { | |
237 | HLT_Output = 0; | |
238 | if (driver_state->hlt_mode == HLT_MODE_BANG) { | |
239 | HLT_Output = (HLT_Input < HLT_Setpoint) ? 1:0; | |
240 | } else if (driver_state->hlt_mode == HLT_MODE_ON) { | |
241 | HLT_Output = 1; | |
242 | } | |
243 | xSemaphoreGive(xSemaphoreDriver); | |
244 | } | |
245 | HLT(HLT_Output); | |
246 | } else if (equipment.SSR2 == SSR2_ON_IDLE) { | |
247 | HLT_Output = 1; | |
248 | HLT(1); | |
249 | } | |
250 | } | |
251 | ||
252 | /* | |
253 | * Independant HLT temperature control | |
254 | */ | |
255 | if (equipment.SSR2 == SSR2_HLT_IND) { | |
256 | if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { | |
257 | HLT_Output = 0; | |
258 | if (driver_state->hlt_mode == HLT_MODE_BANG) { | |
259 | HLT_Output = (HLT_Input < HLT_Setpoint) ? 1:0; | |
260 | } else if (driver_state->hlt_mode == HLT_MODE_ON) { | |
261 | HLT_Output = 1; | |
262 | } | |
263 | xSemaphoreGive(xSemaphoreDriver); | |
264 | } | |
265 | HLT(HLT_Output); | |
266 | } | |
267 | ||
268 | /* | |
269 | * Update the driver results. | |
270 | */ | |
271 | if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { | |
272 | driver_state->mlt_power = (int)((Output * 100) / 255.0); | |
273 | if (HLT_Output) { | |
274 | if (equipment.SSR2 == SSR2_HLT_SHARE) { | |
275 | driver_state->hlt_power = 100 - driver_state->mlt_power; | |
276 | } else if (equipment.SSR2 == SSR2_HLT_IND) { | |
277 | driver_state->hlt_power = 100; | |
278 | } | |
279 | } else { | |
280 | driver_state->hlt_power = 0; | |
281 | } | |
282 | if (driver_state->pump_run != Pump_pin) | |
283 | Pump(driver_state->pump_run); | |
284 | xSemaphoreGive(xSemaphoreDriver); | |
285 | } | |
286 | ||
287 | #if 0 | |
288 | if (rc) { | |
289 | printf("ST: %s MLT[In: %7.3f Out: %3.0f Sp: %6.2f %s RT: %lu] HLT[In: %7.3f Out: %d Sp: %5.1f]\n", outEnable ? "E":"D", | |
290 | Input, Output, Setpoint, PID_GetMode() ? "AUTOMATIC" : "MANUAL ", RealTime, | |
291 | HLT_Input, HLT_Output, HLT_Setpoint); | |
292 | } | |
293 | #endif | |
294 | ||
295 | // Not reliable, so do it manually. | |
296 | //vTaskDelayUntil(&last_wake_time, (1000 / 50) / portTICK_PERIOD_MS); | |
297 | now_tick = xTaskGetTickCount(); | |
298 | if ((now_tick - last_tick) > (1000 / 50)) { | |
299 | // This happens one or two times during a brew. | |
300 | wait_ticks = (1000 / 50); | |
301 | } else { | |
302 | wait_ticks = (1000 / 50) - (now_tick - last_tick); | |
303 | } | |
304 | if (wait_ticks == 0) { | |
305 | // This is rare, but it happens. | |
306 | wait_ticks = 1; | |
307 | } | |
308 | ||
309 | vTaskDelay(wait_ticks); | |
310 | } | |
311 | } | |
312 | ||
313 |