2023-03-03 07:45 AM
Hey,
I'm basicly filling a tank with liquid and I have 2 level sensors in this tank.
These level sensors are connected to GPIO_EXTI of my MCU so when the water reached the sensor level, an interrupt occure.
If the tank is filling or emptying, I must disable the water input regarding to which level the water is (low if emptying or high if filling).
The interruption routine :
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin){
/* Sensor 1 (lower level) interrupt */
if(GPIO_Pin == WATER_SENSOR_LOW_Pin){
if(isEmptying){
StopWaterInput();
}
}
/* Sensor 2 (upper level) interrupt */
else if(GPIO_Pin == WATER_SENSOR_HIGH_Pin){
if(isFilling){
StopWaterOutput();
}
}
}
I need to code a cycle like this :
void Process(){
//Start Fillling the tank
isFilling = 1;
isEmptying = 0;
StartWaterFlowing();
//Water reach low sensor, do nothing
//Water reach high sensor and so the water input is closed (IR callback)
//NEED TO HAVE CLOSED THE WATER INPUT TO CONTINUE
HAL_Delay(5000);
//Start emptying the tank
isFilling = 0;
isEmptying = 1;
StartTankEmptying();
//Water reach high sensor, do nothing
//Water reach low sensor and so the water output is closed (IR callback)
//NEED TO HAVE CLOSED THE WATER OUTPUT TO CONTINUE
}
I don't know how to do for the code to not start emptying the tank until the water reached the high level and reciprocally not start the cycle again until the water reached the low level.
I've read a bit about WFI but I'm not sure that's what I need. I'm also new to thread programming so maybe I need to build the code differently to use thread.
Do you have any suggestion how to acheive this ?
Thanks for your help.
Adrien
2023-03-03 08:16 AM
"Everything should be made as simple as possible, but not simpler." (A. Einstein). Does not look like microseconds or milliseconds matter in your project. I would use a simple state machine approach in the main while loop with polling the sensors and no interrupts which may require special measures against contact bouncing and can be a nightmare for debugging.
hth
KnarfB
2023-03-03 08:23 AM
Hey,
I understand that's something I can do and for testing the prototype it doesn't really matter but this system will be included and copied in a bigger system (with for example 4 of these tanks).
So I would like to write an efficient code that can do other things while the tanks are filling/emptying.
2023-03-03 09:34 AM
Periodic (1s?) timer interrupt with the state-machine logic in the interrupt handler?
Or a FreeRTOS based multi-tasking approach with scheduler friendly osDelay waiting?
Feeding mechanical contacts to interrupts is generally not recommended because of bouncing. Furthermore, a state-machine can be easier understood by others and modified later on, when requirements are changing.
hth
KnarfB
2023-03-04 07:33 AM
Is power a critical resource (running on battery) ?
How slow the reaction time from sensor trigger and acting on it?
Half this time would be the periodic time to wakeup (from RTC?), read the pin levels, act if necessary and sleep again. It has the benefit to enable other functions down the road.
Adding an RTOS is basically running more code to do the same thing, so more power.
I think the power budget, and the operating lifetime will determine the way of implementation.
Mission profiling would be good.
wait for interrupt (WFI) won't save much power. Slowing down or stopping the clock will.
2023-03-05 09:47 PM
@ADesp.1
As @KnarfB mentioned, a state machine would work. You can create a function that accepts a structure data so you can have multiple instances of tanks. Call it from a main while loop and you can do other tasks along with the tank(s). See image to get an idea.
I took up your challenge 😀 so I wrote working code on a STM32F407-DISCO board. I'm using the LED's for indicators for water/drain valves and high/low sensors.
I'm going to make a YouTube tutorial on it. I was using a 4x4 button pad but I had wires going over the LED's so it was hard to seem them work. So I ordered a 4 button Click board and I'm waiting for the shipment to arrive.
Here is the state machine.
void TankControl(TankStruct *tank)
{
switch(tank->stateMachine)
{
case OpenValve:
tank->OpenWaterValve();
++tank->stateMachine;
break;
case WaitForHigh:
if(tank->highWater)
{
++tank->stateMachine;
}
break;
case CloseValve:
tank->CloseWaterValve();
TimerSetValue(tank->timerDelay, 0);// reset timer
++tank->stateMachine;
break;
case PausePriorOpenDrain:
if(TimerGetValue(tank->timerDelay) >= 2000)
{
++tank->stateMachine;
}
break;
case OpenDrain:
tank->OpenDrainValve();
++tank->stateMachine;
break;
case WaitForLow:
if(tank1.lowWater)
{
++tank->stateMachine;
}
break;
case CloseDrain:
tank->CloseDrainValve();
TimerSetValue(tank->timerDelay, 0);// reset timer
++tank->stateMachine;
break;
case PauseAfterCloseDrain:
if(TimerGetValue(tank->timerDelay) >= 2000)
{
tank->stateMachine = OpenValve;
}
break;
}
}
2023-03-06 12:32 AM
Hey, thanks for this answer.
I think this could help a lot, but I migth put the WaitForHigh/CloseValve states in the interruption routine. I do want a lot of precision in the volume control process.
I didn't do a lot of MCU programming so far and I 've never seen this state machine way of coding, I think it's pretty cool.
In the code you increment stateMachine by one at the end of every state. But in the switch case you have an String. What is stateMachine ?
Thx
2023-03-06 01:18 AM
You never want to do anything from within an interrupt routine unless it's a simple GPIO control. You want to set a flag and get out so that other interrupts can work. Plus, the close valve will happen within nano seconds unless you have blocking or inefficient code for other tasks.
stateMachine is just a variable for the tank instance. The cases are enums.
enum StateMachine
{
OpenValve,
WaitForHigh,
CloseValve,
PausePriorOpenDrain,
OpenDrain,
WaitForLow,
CloseDrain,
PauseAfterCloseDrain
};
2023-03-06 01:45 AM
So I should have something like this ?
In th IR callback :
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin){
/* Photo-sensor 1 (lower) interrupt */
if(GPIO_Pin == PHOTO_SENSOR_1_Pin){
tank->lowSensorFlag = 1;
}
/* Photo-sensor 2 (upper) interrupt */
else if(GPIO_Pin == PHOTO_SENSOR_2_Pin){
tank->highSensorFlag = 1;
}
}
In the state machine code :
//...
case WaitForLow:
if(tank->lowSensorFlag)
{
lowSensorFlag = 0;
++tank->stateMachine;
}
break;
//...
case WaitForHigh
if(tank->highSensorFlag)
{
highSensorFlag = 0;
++tank->stateMachine;
}
break;
//...
2023-03-06 08:05 AM
Yes pretty much like that. Though I'm looking at both high and low state so I can control some LED indicators.
/*
* Description: EXTI should be set for external interrupt with high/low edge detection
*
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
uint32_t pinState;
// tank1
if(GPIO_Pin == HighWaterTank1_Pin)
{
pinState = HAL_GPIO_ReadPin(HighWaterTank1_GPIO_Port, HighWaterTank1_Pin);
if(pinState == 0)
{
tank1.highWaterSensor = true;
}
else
{
tank1.highWaterSensor = false;
}
}
else if(GPIO_Pin == LowWaterTank1_Pin)
{
pinState = HAL_GPIO_ReadPin(LowWaterTank1_GPIO_Port, LowWaterTank1_Pin);
if(pinState == 0)
{
tank1.lowWaterSensor = true;
}
else
{
tank1.lowWaterSensor = false;
}
}
// add more for tank2 and so on
}
//sensor status led indicators
void CheckEXTI_Status(TankStruct *tank)
{
if(tank->highWaterSensor)
{
tank->highWaterLedOn();
}
else
{
tank->highWaterLedOff();
}
if(tank->lowWaterSensor)
{
tank->lowWaterLedOn();
}
else
{
tank->lowWaterLedOff();
}
}