Assistance for grain hopper

Hello I am very new to the wonderful world of Arduino.

I have made a basic setup to fill up a grain hopper. What i want to end up with is two levels MIN and MAX, where a relay is triggered for 15 seconds every minute, whenever the MIN level is reached and continue that loop until MAX.

I dont want the loop to start as soon as the level is below MAX. Hope this makes sense.
Attached is a picture of my simle setup. The LED is a substitute for the relay.

Here is my code:

const int trigPin = 9; //Define the HC-SE04 triger on pin 9 on the arduino
const int echoPin = 10; //Define the HC-SE04 echo on pin 10 on the arduino


const int bulb = 4; //Define the relay signal on pin 4 on the arduino
const int MIN = 150; //Minimum level in tank, sensor is meassuring vertically down so above 150 is below min
const int MAX = 50; //Max level in tank
const int MOTOR = 15000; // Fill motor run time in millisec
const int BREAK = 60000; // Cycle time in millisec


void setup()


{


Serial.begin (9600); //Start the serial monitor


pinMode(trigPin, OUTPUT); //set the trigpin to output


pinMode(echoPin, INPUT); //set the echopin to input


pinMode (bulb, OUTPUT); //set the bulb on pin 4 to output (this will be a relay in the finished project)


}


void loop()


{


int duration, distance; //Define two intregers duration and distance to be used to save data


digitalWrite(trigPin, HIGH); //write a digital high to the trigpin to send out the pulse


delayMicroseconds(500); //wait half a millisecond


digitalWrite(trigPin, LOW); //turn off the trigpin


duration = pulseIn(echoPin, HIGH); //measure the time using pulsein when the echo receives a signal set it to high


distance = (duration/2) / 29.1; //distance is the duration devided by 2 becasue the signal traveled from the trigpin then back to the echo pin, then divide by 29.1 to convert to centimeters


if (distance < MIN && distance > MAX) //run fill loop if distance is between MIN and MAX


{
digitalWrite(bulb,HIGH);
delay(MOTOR);
digitalWrite(bulb,LOW);
delay(BREAK);
}


else
{
delay(BREAK);
}


Serial.print(distance); //Dispaly the distance on the serial monitor


Serial.println(" CM"); //in centimeters


delay(500); //delay half a second


}

I have tried to read up on bool and boolean but cannot get it to make sense in my brain.
Any assistance is greatly appreciated.

Use a boolean. Call it FillGrainHopper. Set it false in setup.

In loop, check the level. If it is MIN or lower, set the variable true. If it is MAX or higher, set it false.

If the variable is true, run the motor to add grain for 15 seconds.

this seems like a pretty simple state machine to code. It would look like this:

Your specification does not say what to do if MIN is reached when you are pausing for 45s
(it could possibly happen when the relay is ON but let’s assume this is not the case)

wildbill: Use a boolean. Call it FillGrainHopper. Set it false in setup.

In loop, check the level. If it is MIN or lower, set the variable true. If it is MAX or higher, set it false.

If the variable is true, run the motor to add grain for 15 seconds.

Hey Bill Yeah i know a boolean is an answer, but as i mentioned in my post i an unaware on exactly how to integrate it, as this is my first time getting my hands on coding.

You have managed to declare consts and variables. Try declaring a global boolean and set it false in setup. Post your attempt if it doesn't work.

coding a state machine is also just a matter of looking at the arrows leaving each state and checking if the condition (event) has happened in which case you are performing the actions and you transition states.

The diagram above would look something like this. pretty straightforward to read in my opinion

enum t_states : uint8_t {SYSTEM_IDLE, RELAY_ON, RELAY_OFF} currentState = SYSTEM_IDLE;
const uint8_t relayPin = 2;

const uint16_t DMIN = 200 - 150; // assuming sensor set 2m above bottom of grain hopper
const uint16_t DMAX = 200 - 50;

uint16_t getDistance()
{
  uint16_t cm = 0;
  // TO DO
  return cm;
}

void runGrainHopper()
{
  static uint32_t t0;
  uint16_t d = getDistance();
  switch (currentState) {
    case SYSTEM_IDLE:
      if (d < DMIN) {
        digitalWrite(relayPin, HIGH);
        t0 = millis();
        currentState = RELAY_ON;
      }
      break;

    case RELAY_ON:
      if (d > DMAX) {
        digitalWrite(relayPin, LOW);
        currentState = SYSTEM_IDLE;
      } else if (millis() - t0 > 15000UL) {
        digitalWrite(relayPin, LOW);
        t0 = millis();
        currentState = RELAY_OFF;
      }
      break;

    case RELAY_OFF:
      if (d < DMIN) {
        // DECIDE WHAT TO DO ?????
      } else if (millis() - t0 > 45000UL) {
        digitalWrite(relayPin, HIGH);
        t0 = millis();
        currentState = RELAY_ON;
      }
      break;
  }
}

void setup() {
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, LOW);
}

void loop()
{
  runGrainHopper();
  // you can do other stuff here
}

This is typed here, I’ve not clue if it compiles but that could get you started

this might feel it’s over-engineered for your case (a bool could do) but it’s very easy to evolve such an architecture if you expand constraints, sensors etc…

this is also non blocking (see there is no delay() in the code) so you can do many other (non blocking) things at the same time.

J-M-L: It would look like this:

karma+ for the drawing.

@TO: if you have the need to explain your description with

Hope this makes sense.

add a schematic/drawing!

https://en.wikipedia.org/wiki/Finite-state_machine

It would look like this

J-M-L, what did you use to draw that?

That’s just with Keynote (something like PowerPoint in Apple’s realm, it’s a free app)

J-M-L: That’s just with Keynote (something like PowerPoint in Apple’s realm, it’s a free app)

Ah ok, I normally use PowerPoint. I have a slide containing all the bits I normally use, circles, arrows, text boxes etc etc and just make a dup of that every time I need a new diagram, and just copy/paste all the bits as I need them.

J-M-L:
coding a state machine is also just a matter of looking at the arrows leaving each state and checking if the condition (event) has happened in which case you are performing the actions and you transition states.

The diagram above would look something like this. pretty straightforward to read in my opinion

enum t_states : uint8_t {SYSTEM_IDLE, RELAY_ON, RELAY_OFF} currentState = SYSTEM_IDLE;

const uint8_t relayPin = 2;

const uint8_t DMIN = 200 - 150; // assuming sensor set 2m above bottom of grain hopper
const uint8_t DMAX = 200 - 50;

uint8_t getDistance()
{
 uint8_t cm = 0;
 // TO DO
 return cm;
}

void runGrainHopper()
{
 static unsigned long t0;
 uint8_t d = getDistance();
 switch (currentState) {
   case SYSTEM_IDLE:
     if (d < DMIN) {
       digitalWrite(relayPin, HIGH);
       t0 = millis();
       currentState = RELAY_ON;
     }
     break;

case RELAY_ON:
     if (d > DMAX) {
       digitalWrite(relayPin, LOW);
       currentState = SYSTEM_IDLE;
     } else if (millis() - t0 > 15000UL) {
       digitalWrite(relayPin, LOW);
       t0 = millis();
       currentState = RELAY_OFF;
     }
     break;

case RELAY_OFF:
     if (d < DMIN) {
       // DECIDE WHAT TO DO ???
     } else if (millis() - t0 > 45000UL) {
       digitalWrite(relayPin, HIGH);
       t0 = millis();
       currentState = RELAY_ON;
     }
     break;
 }
}

void setup() {
 pinMode(relayPin, OUTPUT);
 digitalWrite(relayPin, LOW);
}

void loop()
{
 runGrainHopper();
 // you can do other stuff here
}



This is typed here, I've not clue if it compiles but that could get you started

this might feel it's over-engineered for your case (a bool could do) but it's very easy to evolve such an architecture if you expand constraints, sensors etc...

this is also non blocking (see there is no delay() in the code) so you can do many other (non blocking) things at the same time.

Hello again J-M-L
I see that state machine is the way to go, and I see the progression as logical up until i try to integrate it with the ultrasonic sensoe. I have no idea where to integrate trig and echo pins and get my distance figure for the state changes.
Hope you, or anyone else will be able to help.
I have tried with boolean but cant get the parameter where it starts when min distance is met and continues until max and then wait until min is met again. See code.

const int trigPin = 9; //Define the HC-SE04 triger on pin 9 on the arduino
const int echoPin = 10; //Define the HC-SE04 echo on pin 10 on the arduino


const int bulb = 4; //Define the relay signal on pin 4 on the arduino
const int MIN = 150; //Minimum level in tank, sensor is meassuring vertically down so above 150 is below min
const int MAX = 50; //Max level in tank
const int MOTOR = 1000; // Fill motor run time in millisec
const int BREAK = 500; // Cycle time in millisec


boolean FillGrain=false;


void setup()


{


Serial.begin (9600); //Start the serial monitor


pinMode(trigPin, OUTPUT); //set the trigpin to output


pinMode(echoPin, INPUT); //set the echopin to input


pinMode (bulb, OUTPUT); //set the bulb on pin 4 to output (this will be a relay in the finished project)


}


void loop()


{


int duration, distance; //Define two intregers duration and distance to be used to save data


digitalWrite(trigPin, HIGH); //write a digital high to the trigpin to send out the pulse


delayMicroseconds(500); //wait half a millisecond


digitalWrite(trigPin, LOW); //turn off the trigpin


duration = pulseIn(echoPin, HIGH); //measure the time using pulsein when the echo receives a signal set it to high


distance = (duration/2) / 29.1; //distance is the duration devided by 2 becasue the signal traveled from the trigpin then back to the echo pin, then divide by 29.1 to convert to centimeters


  if (distance > MIN)
  {
    FillGrain = true;
  }
  
if (distance < MAX)
{
  FillGrain = false;
}
 
  
  if(distance > MIN && FillGrain == true)
{
digitalWrite(bulb,HIGH);
delay(MOTOR);
digitalWrite(bulb,LOW);
delay(BREAK);
}




Serial.print(distance); //Dispaly the distance on the serial monitor


Serial.println(" CM"); //in centimeters


delay(500); //delay half a second


}

I thought you wanted to run the motor for 15S each time - I assumed because dust confuses the sensor. Is that no longer true?

This is the code performing a distance reading

int duration, distance; //Define two intregers duration and distance to be used to save data

digitalWrite(trigPin, HIGH); //write a digital high to the trigpin to send out the pulse
delayMicroseconds(500); //wait half a millisecond
digitalWrite(trigPin, LOW); //turn off the trigpin
duration = pulseIn(echoPin, HIGH); //measure the time using pulsein when the echo receives a signal set it to high
distance = (duration/2) / 29.1; //distance is the duration devided by 2 becasue the signal traveled from the trigpin then back to the echo pin, then divide by 29.1 to convert to centimeters

I provided a function where you need to read and return the distance in cm

 uint16_t getDistance()
{
  uint16_t cm = 0;
  // TO DO
  return cm;
}

You are not very far...

wildbill: I thought you wanted to run the motor for 15S each time - I assumed because dust confuses the sensor. Is that no longer true?

That is still true. I just lowered them in order to tinker. So i didnt have to wait so long to se the response.

Try changing this:

  if (distance > MIN && FillGrain == true)

To this:

  if (FillGrain)

wildbill: Try changing this:

  if (distance > MIN && FillGrain == true)

To this:

  if (FillGrain)

Bill you are a genious!! Thank you so very much. Amazing how the smallest things fix stuff when coding!

Note that When you have this code

 if(FillGrain) {
  digitalWrite(bulb,HIGH);
  delay(MOTOR);
  digitalWrite(bulb,LOW);
  delay(BREAK);
}

you don’t check for levels and so you will possibly overshoot if you reach the MAX during execution. (In the worst case you’ll have the bulb HIGH for 15 seconds after reaching MAX level. I don’t know if that’s a problem or not in real life.

The year I got married, 1961, I ran a grass seed cleaning mill 13 hours every night except Sunday. I can tell you right now there are several problems with your project. 1) Your grain will pile up and make a cone shaped mound. 2) grain will ABSORB the sound, not reflect it. 3) grain in a mound will absorb even more sound 4) depending on the shape of the container, you will get multiple reflections from the sides of the container.

The bins I worked with had windows so you can see the level of the grain. You might be able to do something similar.

Good luck, Paul