Wed, 16 Jun 2021 15:13:36 +0200
Idee voor HendiControl sturing
/** * @file task_driver.c * @brief BrewBoard relays driver. Control the hardware outputs with the * Solid State relays for the Mash/Boil kettle (MLT, the Hot * Liquer Tank (HLT) and the pump. The MLT has a PID controller * during mashing, and a simple bang on/off control during the * boil. * The HLT output can be off, bang on/off, or just on if the MLT * is off, depending on the configuration. * Use SSR modules that switch during zero crossing of the mains * power, so that when one is turned on and on the same time the * other is turned off, they won't be active at the same time. */ #include "config.h" #define SSR_MLT CONFIG_SSR_MLT_GPIO ///< GPIO SSR MLT pin #define SSR_HLT CONFIG_SSR_HLT_GPIO ///< GPIO SSR HLT pin #define SSR_PUMP CONFIG_SSR_PUMP_GPIO ///< GPIO Pump relay pin bool outEnable = false; ///< Enable outputs flag DRIVER_State * driver_state; ///< Driver state SemaphoreHandle_t xSemaphoreDriver = NULL; ///< Driver state lock int MLT_pin = 0; ///< MLT state int HLT_pin = 0; ///< HLT state int Pump_pin = 0; ///< Pump state double Input = 0; ///< PID input value double Output = 0; ///< PID output value double Setpoint = 0; ///< PID setpoint value int MLT_Mode = MLT_MODE_NONE; ///< MLT mode flag double HLT_Input = 0; ///< HLT input value double HLT_Setpoint = 0; ///< HLT setpoint values int HLT_Output = 0; ///< HLT output value int HLT_Mode = HLT_MODE_NONE; ///< HLT mode flag TickType_t MLT_time, HLT_time; static const char *MLTTypes[] = { "None", "Bang", "PID", "Off", "Ext" }; static const char *HLTTypes[] = { "None", "Bang", "Off", "On" }; static const char *TAG = "task_driver"; extern SemaphoreHandle_t xSemaphoreDS18B20; extern DS18B20_State *ds18b20_state; extern unsigned long lastTime; /** * @brief Turn the MLT SSR on or off. */ void MLT(int onoff); /** * @brief Turn the HLT SSR on or off. */ void HLT(int onoff); /** * @brief Turn the Pump on or off. */ void Pump(int onoff); void MLT(int onoff) { if (onoff && outEnable) { if (MLT_pin != 1) MLT_time = xTaskGetTickCount(); gpio_set_level(SSR_MLT, 1); MLT_pin = 1; } else { if (MLT_pin) runtime.MLT_usage += xTaskGetTickCount() - MLT_time; gpio_set_level(SSR_MLT, 0); MLT_pin = 0; } } void HLT(int onoff) { if (onoff && outEnable) { if (HLT_pin != 1) HLT_time = xTaskGetTickCount(); gpio_set_level(SSR_HLT, 1); HLT_pin = 1; } else { if (HLT_pin) runtime.HLT_usage += xTaskGetTickCount() - HLT_time; gpio_set_level(SSR_HLT, 0); HLT_pin = 0; } } void Pump(int onoff) { if (onoff && outEnable) { gpio_set_level(SSR_PUMP, 1); Pump_pin = 1; } else { gpio_set_level(SSR_PUMP, 0); Pump_pin = 0; } } /** * @brief Load PID settings from equipment record. */ void LoadPIDsettings() { PID_SetTunings(equipment.PID_kP, equipment.PID_kI, equipment.PID_kD); PID_SetSampleTime(equipment.SampleTime); /* * Initialize the PID */ Output = 0.0; // Reset internal Iterm. PID_SetMode(PID_MANUAL); PID_SetMode(PID_AUTOMATIC); } void task_driver(void *pvParameter) { TickType_t wait_ticks, last_tick, now_tick; bool rc; unsigned long now, RealTime, w_StartTime = 0; ESP_LOGI(TAG, "Start drivers"); /* * Configure IOMUX register. */ gpio_pad_select_gpio(SSR_MLT); gpio_set_direction(SSR_MLT, GPIO_MODE_OUTPUT); gpio_pad_select_gpio(SSR_HLT); gpio_set_direction(SSR_HLT, GPIO_MODE_OUTPUT); gpio_pad_select_gpio(SSR_PUMP); gpio_set_direction(SSR_PUMP, GPIO_MODE_OUTPUT); /* * Initialize state */ driver_state = malloc(sizeof(DRIVER_State)); driver_state->enable = outEnable = false; driver_state->mlt_gpio = SSR_MLT; driver_state->mlt_mode = MLT_MODE_NONE; driver_state->mlt_sp = driver_state->mlt_pv = 0.0; driver_state->mlt_power = 0; driver_state->hlt_gpio = SSR_HLT; driver_state->hlt_mode = HLT_MODE_NONE; driver_state->hlt_sp = driver_state->hlt_pv = 0.0; driver_state->hlt_power = 0; driver_state->hlt_and_mlt = false; driver_state->pump_gpio = SSR_PUMP; driver_state->pump_run = 0; PID(&Input, &Output, &Setpoint, 200, 2.0, 1.5, PID_DIRECT); /* * One loop must complete in 20 mSecs, that is one mains * frequency period cycle in 50 Hz countries. */ while (1) { last_tick = xTaskGetTickCount(); if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { /* * Get the current temperature readings */ if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { if (ds18b20_state->mlt_valid) driver_state->mlt_pv = ds18b20_state->mlt_temperature; if (ds18b20_state->hlt_valid) driver_state->hlt_pv = ds18b20_state->hlt_temperature; xSemaphoreGive(xSemaphoreDS18B20); } /* * Other values that we need */ Input = driver_state->mlt_pv; Setpoint = driver_state->mlt_sp; if (driver_state->mlt_mode != MLT_Mode) { if (driver_state->mlt_mode == MLT_MODE_BANG) { PID_SetMode(PID_MANUAL); } else if (driver_state->mlt_mode == MLT_MODE_PID) { LoadPIDsettings(); } MLT_Mode = driver_state->mlt_mode; ESP_LOGI(TAG, "MLT mode set to %s", MLTTypes[MLT_Mode]); } if (driver_state->hlt_mode != HLT_Mode) { HLT_Mode = driver_state->hlt_mode; ESP_LOGI(TAG, "HLT mode set to %s", HLTTypes[HLT_Mode]); } outEnable = driver_state->enable; HLT_Input = driver_state->hlt_pv; HLT_Setpoint = driver_state->hlt_sp; xSemaphoreGive(xSemaphoreDriver); } rc = false; now = xTaskGetTickCount() * portTICK_PERIOD_MS; if ((PID_GetMode() == PID_AUTOMATIC) && (MLT_Mode == MLT_MODE_PID)) { rc = PID_Compute(); RealTime = (equipment.SampleTime * equipment.MashPower) / 100; } else { /* * Schedule the loop ourself. */ unsigned long timeChange = (now - lastTime); if (timeChange >= equipment.SampleTime) { lastTime = now; rc = true; } RealTime = equipment.SampleTime; if (driver_state->mlt_mode == MLT_MODE_BANG) { Output = (Input < Setpoint) ? 255:0; } if (driver_state->mlt_mode == MLT_MODE_NONE || driver_state->mlt_mode == MLT_MODE_OFF) { Output = 0; } } if (rc) { w_StartTime = now; } if ((int)((Output / 255.0) * RealTime) > (now - w_StartTime)) { MLT(1); if ((equipment.SSR2 == SSR2_HLT_SHARE) || (equipment.SSR2 == SSR2_ON_IDLE)) { HLT(0); HLT_Output = 0; } } else { MLT(0); if (equipment.SSR2 == SSR2_HLT_SHARE) { if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { HLT_Output = 0; if (driver_state->hlt_mode == HLT_MODE_BANG) { HLT_Output = (HLT_Input < HLT_Setpoint) ? 1:0; } else if (driver_state->hlt_mode == HLT_MODE_ON) { HLT_Output = 1; } xSemaphoreGive(xSemaphoreDriver); } HLT(HLT_Output); } else if (equipment.SSR2 == SSR2_ON_IDLE) { HLT_Output = 1; HLT(1); } } /* * Independant HLT temperature control */ if (equipment.SSR2 == SSR2_HLT_IND) { if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { HLT_Output = 0; if (driver_state->hlt_mode == HLT_MODE_BANG) { HLT_Output = (HLT_Input < HLT_Setpoint) ? 1:0; } else if (driver_state->hlt_mode == HLT_MODE_ON) { HLT_Output = 1; } xSemaphoreGive(xSemaphoreDriver); } HLT(HLT_Output); } /* * Update the driver results. */ if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { driver_state->mlt_power = (int)((Output * 100) / 255.0); if (HLT_Output) { if (equipment.SSR2 == SSR2_HLT_SHARE) { driver_state->hlt_power = 100 - driver_state->mlt_power; } else if (equipment.SSR2 == SSR2_HLT_IND) { driver_state->hlt_power = 100; } } else { driver_state->hlt_power = 0; } if (driver_state->pump_run != Pump_pin) Pump(driver_state->pump_run); xSemaphoreGive(xSemaphoreDriver); } #if 0 if (rc) { 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", Input, Output, Setpoint, PID_GetMode() ? "AUTOMATIC" : "MANUAL ", RealTime, HLT_Input, HLT_Output, HLT_Setpoint); } #endif // Not reliable, so do it manually. //vTaskDelayUntil(&last_wake_time, (1000 / 50) / portTICK_PERIOD_MS); now_tick = xTaskGetTickCount(); if ((now_tick - last_tick) > (1000 / 50)) { // This happens one or two times during a brew. wait_ticks = (1000 / 50); } else { wait_ticks = (1000 / 50) - (now_tick - last_tick); } if (wait_ticks == 0) { // This is rare, but it happens. wait_ticks = 1; } vTaskDelay(wait_ticks); } }