Sat, 20 Oct 2018 13:23:15 +0200
Initial checkin brewboard
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 | ||
18 | #define SSR_MLT CONFIG_SSR_MLT_GPIO | |
19 | #define SSR_HLT CONFIG_SSR_HLT_GPIO | |
20 | #define SSR_PUMP CONFIG_SSR_PUMP_GPIO | |
21 | ||
22 | ||
23 | bool outEnable = false; | |
24 | DRIVER_State * driver_state; | |
25 | SemaphoreHandle_t xSemaphoreDriver = NULL; | |
26 | int MLT_pin = 0; | |
27 | int HLT_pin = 0; | |
28 | int Pump_pin = 0; | |
29 | double Input = 0, Output = 0, Setpoint = 0; | |
30 | int MLT_Mode = MLT_MODE_NONE; | |
31 | double HLT_Input = 0, HLT_Setpoint = 0; | |
32 | int HLT_Output = 0; | |
33 | int HLT_Mode = HLT_MODE_NONE; | |
34 | ||
35 | static const char *TAG = "task_driver"; | |
36 | ||
37 | extern SemaphoreHandle_t xSemaphoreDS18B20; | |
38 | extern DS18B20_State * ds18b20_state; | |
39 | extern unsigned long lastTime; | |
40 | ||
41 | ||
42 | /** | |
43 | * @brief Turn the MLT SSR on or off. | |
44 | */ | |
45 | void MLT(int onoff); | |
46 | ||
47 | /** | |
48 | * @brief Turn the HLT SSR on or off. | |
49 | */ | |
50 | void HLT(int onoff); | |
51 | ||
52 | /** | |
53 | * @brief Turn the Pump on or off. | |
54 | */ | |
55 | void Pump(int onoff); | |
56 | ||
57 | ||
58 | ||
59 | void MLT(int onoff) { | |
60 | ||
61 | if (onoff && outEnable) { | |
62 | gpio_set_level(SSR_MLT, 1); | |
63 | MLT_pin = 1; | |
64 | } else { | |
65 | gpio_set_level(SSR_MLT, 0); | |
66 | MLT_pin = 0; | |
67 | } | |
68 | } | |
69 | ||
70 | ||
71 | ||
72 | void HLT(int onoff) { | |
73 | ||
74 | if (onoff && outEnable) { | |
75 | gpio_set_level(SSR_HLT, 1); | |
76 | HLT_pin = 1; | |
77 | } else { | |
78 | gpio_set_level(SSR_HLT, 0); | |
79 | HLT_pin = 0; | |
80 | } | |
81 | } | |
82 | ||
83 | ||
84 | ||
85 | void Pump(int onoff) { | |
86 | ||
87 | if (onoff && outEnable) { | |
88 | gpio_set_level(SSR_PUMP, 1); | |
89 | Pump_pin = 1; | |
90 | } else { | |
91 | gpio_set_level(SSR_PUMP, 0); | |
92 | Pump_pin = 0; | |
93 | } | |
94 | } | |
95 | ||
96 | ||
97 | ||
98 | void LoadPIDsettings() { | |
99 | PID_SetTunings(equipment.PID_kP, equipment.PID_kI, equipment.PID_kD, equipment.PID_POn); | |
100 | PID_SetSampleTime(equipment.SampleTime); | |
101 | ||
102 | /* | |
103 | * Initialize the PID | |
104 | */ | |
105 | Output = 0.0; // Reset internal Iterm. | |
106 | PID_SetMode(PID_MANUAL); | |
107 | PID_SetMode(PID_AUTOMATIC); | |
108 | } | |
109 | ||
110 | ||
111 | ||
112 | void task_driver(void *pvParameter) | |
113 | { | |
114 | TickType_t wait_ticks, last_tick, now_tick; | |
115 | bool rc; | |
116 | unsigned long now, RealTime, w_StartTime = 0; | |
117 | ||
118 | ESP_LOGI(TAG, "Starting output drivers"); | |
119 | ||
120 | /* | |
121 | * Configure IOMUX register. | |
122 | */ | |
123 | gpio_pad_select_gpio(SSR_MLT); | |
124 | gpio_set_direction(SSR_MLT, GPIO_MODE_OUTPUT); | |
125 | gpio_pad_select_gpio(SSR_HLT); | |
126 | gpio_set_direction(SSR_HLT, GPIO_MODE_OUTPUT); | |
127 | gpio_pad_select_gpio(SSR_PUMP); | |
128 | gpio_set_direction(SSR_PUMP, GPIO_MODE_OUTPUT); | |
129 | ||
130 | /* | |
131 | * Initialize state | |
132 | */ | |
133 | driver_state = malloc(sizeof(DRIVER_State)); | |
134 | driver_state->enable = outEnable = false; | |
135 | driver_state->mlt_gpio = SSR_MLT; | |
136 | driver_state->mlt_mode = MLT_MODE_NONE; | |
137 | driver_state->mlt_sp = driver_state->mlt_pv = 0.0; | |
138 | driver_state->mlt_power = 0; | |
139 | driver_state->hlt_gpio = SSR_HLT; | |
140 | driver_state->hlt_mode = HLT_MODE_NONE; | |
141 | driver_state->hlt_sp = driver_state->hlt_pv = 0.0; | |
142 | driver_state->hlt_power = 0; | |
143 | driver_state->hlt_and_mlt = false; | |
144 | driver_state->pump_gpio = SSR_PUMP; | |
145 | driver_state->pump_run = 0; | |
146 | ||
147 | PID(&Input, &Output, &Setpoint, 150, 1.5, 15000, PID_P_ON_E, PID_DIRECT); | |
148 | ||
149 | /* | |
150 | * One loop must complete in 20 mSecs, that is one mains | |
151 | * frequency period cycle in 50 Hz countries. | |
152 | */ | |
153 | while (1) { | |
154 | ||
155 | last_tick = xTaskGetTickCount(); | |
156 | ||
157 | if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { | |
158 | /* | |
159 | * Get the current temperature readings | |
160 | */ | |
161 | if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { | |
162 | if (ds18b20_state->mlt_valid) | |
163 | driver_state->mlt_pv = ds18b20_state->mlt_temperature; | |
164 | if (ds18b20_state->hlt_valid) | |
165 | driver_state->hlt_pv = ds18b20_state->hlt_temperature; | |
166 | xSemaphoreGive(xSemaphoreDS18B20); | |
167 | } | |
168 | ||
169 | /* | |
170 | * Other values that we need | |
171 | */ | |
172 | Input = driver_state->mlt_pv; | |
173 | Setpoint = driver_state->mlt_sp; | |
174 | if (driver_state->mlt_mode != MLT_Mode) { | |
175 | if (driver_state->mlt_mode == MLT_MODE_BANG) { | |
176 | PID_SetMode(PID_MANUAL); | |
177 | } else if (driver_state->mlt_mode == MLT_MODE_PID) { | |
178 | LoadPIDsettings(); | |
179 | } | |
180 | MLT_Mode = driver_state->mlt_mode; | |
181 | ESP_LOGI(TAG, "MLT mode set to %d", MLT_Mode); | |
182 | } | |
183 | if (driver_state->hlt_mode != HLT_Mode) { | |
184 | HLT_Mode = driver_state->hlt_mode; | |
185 | ESP_LOGI(TAG, "HLT mode set to %d", HLT_Mode); | |
186 | } | |
187 | outEnable = driver_state->enable; | |
188 | HLT_Input = driver_state->hlt_pv; | |
189 | HLT_Setpoint = driver_state->hlt_sp; | |
190 | xSemaphoreGive(xSemaphoreDriver); | |
191 | } | |
192 | ||
193 | rc = false; | |
194 | now = xTaskGetTickCount() * portTICK_PERIOD_MS; | |
195 | ||
196 | if ((PID_GetMode() == PID_AUTOMATIC) && (MLT_Mode == MLT_MODE_PID)) { | |
197 | rc = PID_Compute(); | |
198 | RealTime = (equipment.SampleTime * equipment.MashPower) / 100; | |
199 | } else { | |
200 | /* | |
201 | * Schedule the loop ourself. | |
202 | */ | |
203 | unsigned long timeChange = (now - lastTime); | |
204 | if (timeChange >= equipment.SampleTime) { | |
205 | lastTime = now; | |
206 | rc = true; | |
207 | } | |
208 | RealTime = equipment.SampleTime; | |
209 | if (driver_state->mlt_mode == MLT_MODE_BANG) { | |
210 | Output = (Input < Setpoint) ? 255:0; | |
211 | } | |
212 | if (driver_state->mlt_mode == MLT_MODE_NONE || driver_state->mlt_mode == MLT_MODE_OFF) { | |
213 | Output = 0; | |
214 | } | |
215 | } | |
216 | ||
217 | if (rc) { | |
218 | w_StartTime = now; | |
219 | } | |
220 | ||
221 | if ((int)((Output / 255.0) * RealTime) > (now - w_StartTime)) { | |
222 | MLT(1); | |
223 | if ((equipment.SSR2 == SSR2_HLT_SHARE) || (equipment.SSR2 == SSR2_ON_IDLE)) { | |
224 | HLT(0); | |
225 | HLT_Output = 0; | |
226 | } | |
227 | } else { | |
228 | MLT(0); | |
229 | if (equipment.SSR2 == SSR2_HLT_SHARE) { | |
230 | if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { | |
231 | HLT_Output = 0; | |
232 | if (driver_state->hlt_mode == HLT_MODE_BANG) { | |
233 | HLT_Output = (HLT_Input < HLT_Setpoint) ? 1:0; | |
234 | } else if (driver_state->hlt_mode == HLT_MODE_ON) { | |
235 | HLT_Output = 1; | |
236 | } | |
237 | xSemaphoreGive(xSemaphoreDriver); | |
238 | } | |
239 | HLT(HLT_Output); | |
240 | } else if (equipment.SSR2 == SSR2_ON_IDLE) { | |
241 | HLT_Output = 1; | |
242 | HLT(1); | |
243 | } | |
244 | } | |
245 | ||
246 | /* | |
247 | * Independant HLT temperature control | |
248 | */ | |
249 | if (equipment.SSR2 == SSR2_HLT_IND) { | |
250 | if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { | |
251 | HLT_Output = 0; | |
252 | if (driver_state->hlt_mode == HLT_MODE_BANG) { | |
253 | HLT_Output = (HLT_Input < HLT_Setpoint) ? 1:0; | |
254 | } else if (driver_state->hlt_mode == HLT_MODE_ON) { | |
255 | HLT_Output = 1; | |
256 | } | |
257 | xSemaphoreGive(xSemaphoreDriver); | |
258 | } | |
259 | HLT(HLT_Output); | |
260 | } | |
261 | ||
262 | /* | |
263 | * Update the driver results. | |
264 | */ | |
265 | if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { | |
266 | driver_state->mlt_power = (int)((Output * 100) / 255.0); | |
267 | if (HLT_Output) { | |
268 | if (equipment.SSR2 == SSR2_HLT_SHARE) { | |
269 | driver_state->hlt_power = 100 - driver_state->mlt_power; | |
270 | } else if (equipment.SSR2 == SSR2_HLT_IND) { | |
271 | driver_state->hlt_power = 100; | |
272 | } | |
273 | } else { | |
274 | driver_state->hlt_power = 0; | |
275 | } | |
276 | if (driver_state->pump_run != Pump_pin) | |
277 | Pump(driver_state->pump_run); | |
278 | xSemaphoreGive(xSemaphoreDriver); | |
279 | } | |
280 | ||
281 | #if 0 | |
282 | if (rc) { | |
283 | 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", | |
284 | Input, Output, Setpoint, PID_GetMode() ? "AUTOMATIC" : "MANUAL ", RealTime, | |
285 | HLT_Input, HLT_Output, HLT_Setpoint); | |
286 | } | |
287 | #endif | |
288 | ||
289 | // Not reliable, so do it manually. | |
290 | //vTaskDelayUntil(&last_wake_time, (1000 / 50) / portTICK_PERIOD_MS); | |
291 | now_tick = xTaskGetTickCount(); | |
292 | if ((now_tick - last_tick) > (1000 / 50)) { | |
293 | // This happens one or two times during a brew. | |
294 | wait_ticks = (1000 / 50); | |
295 | } else { | |
296 | wait_ticks = (1000 / 50) - (now_tick - last_tick); | |
297 | } | |
298 | if (wait_ticks == 0) { | |
299 | // This is rare, but it happens. | |
300 | wait_ticks = 1; | |
301 | } | |
302 | ||
303 | vTaskDelay(wait_ticks); | |
304 | } | |
305 | } | |
306 | ||
307 |