Configuration persisted in flash
Core/Src/main.c typedef struct {
uint32_t magic;
uint32_t Sensor1DryValue;
uint32_t Sensor1WetValue;
uint32_t Sensor2DryValue;
uint32_t Sensor2WetValue;
uint32_t GetMoistureAvg_TimeLenghtDefault;
uint32_t GetMoistureAvg_TimeLenghtFast;
uint32_t StartPumpWhenMoistureLower;
uint32_t StopPumpWhenMoistureHigher;
uint32_t lightTime;
} DataFlash;
if (dane.magic != FLASH_MAGIC) {
dane.Sensor1DryValue = 2700;
dane.Sensor1WetValue = 2020;
dane.StartPumpWhenMoistureLower = 40;
dane.StopPumpWhenMoistureHigher = 75;
dane.lightTime = 3600 * 1000 * 10;
memoryPageErase(0);
Flash_WriteStruct(flash_address, &dane, sizeof(DataFlash));
}
This code turns the project into a real device, not a one-off test. Sensor calibration, pump thresholds and lighting time survive reset. The magic value detects whether flash contains valid data.
UART interface for calibration and testing
Core/Src/main.c while (USART_TakeLine(line, sizeof(line))) {
matched |= parse_kv_int(line, "S1DValue", &dane.Sensor1DryValue);
matched |= parse_kv_int(line, "S1WValue", &dane.Sensor1WetValue);
matched |= parse_kv_int(line, "StartPumpWhenMoistureLower",
&dane.StartPumpWhenMoistureLower);
matched |= parse_kv_int(line, "StopPumpWhenMoistureHigher",
&dane.StopPumpWhenMoistureHigher);
if (matched) {
memoryPageErase(0);
Flash_WriteStruct(flash_address, &dane, sizeof(DataFlash));
}
}
The firmware accepts key:value text commands. This allows dry/wet soil calibration and pump thresholds to be changed from a UART terminal without editing code or reflashing.
Pump control with hysteresis and water-level protection
Core/Src/main.c MoistureGeneral =
(MoistureHumidityPercent_Dev_1 + MoistureHumidityPercent_Dev_2) / 2;
if (WaterSensor1State != GPIO_PIN_SET) {
if (MoistureGeneral <= dane.StartPumpWhenMoistureLower &&
PumpIsRunnign == false) {
HAL_GPIO_WritePin(PIN_PB10_Pump_GPIO_Port, PIN_PB10_Pump_Pin, GPIO_PIN_SET);
PumpIsRunnign = true;
}
}
if (MoistureGeneral >= dane.StopPumpWhenMoistureHigher &&
PumpIsRunnign == true) {
HAL_GPIO_WritePin(PIN_PB10_Pump_GPIO_Port, PIN_PB10_Pump_Pin, GPIO_PIN_RESET);
PumpIsRunnign = false;
}
The code averages two moisture sensors and controls the pump from the general value. Two thresholds create hysteresis, so the pump does not chatter near the boundary. The water-level sensor blocks pump start.
Grow-light control logic
Core/Src/main.c if (LightSensorPercent <= DARK)
LightSensor = DARK;
else if (LightSensorPercent > DARK && LightSensorPercent < BRIGHT)
LightSensor = NIGHT_OR_MORNING;
else if (LightSensorPercent > BRIGHT)
LightSensor = BRIGHT;
switch (LightSensor) {
case NIGHT_OR_MORNING:
if (LightTimerEnabled == false && waitFordark == false) {
GetLightTime_FromTime = TimeNOW;
LightTimerEnabled = true;
}
HAL_GPIO_WritePin(PIN_PB11_Light_GPIO_Port, PIN_PB11_Light_Pin, GPIO_PIN_SET);
break;
case BRIGHT:
HAL_GPIO_WritePin(PIN_PB11_Light_GPIO_Port, PIN_PB11_Light_Pin, GPIO_PIN_RESET);
break;
}
Lighting is not controlled by a single brightness threshold. The code detects a transition range, starts a lighting time window and disables the light when ambient brightness is too high.
Water mixing function
Core/Src/main.c MixWater_State MixWaterState = MIX_WATER_IDLE;
const uint32_t MixWaterPulseTime = 5000;
const uint32_t MixWaterPauseTime = 5000;
switch (MixWaterState)
{
case MIX_WATER_PULSE_1_ON:
// Turn pump on for MixWaterPulseTime
case MIX_WATER_PULSE_1_OFF:
// Turn pump off for MixWaterPauseTime
case MIX_WATER_PULSE_2_ON:
// Turn pump on for MixWaterPulseTime again
}
The water mixing function implements periodic water mixing every 3 hours with pulse and pause phases. It enables even distribution of fertilizers in the system.