After many hours and days trying to sort out what doesn't work with my code, I'm seeking help.
The code below is a small part of a larger project and it is the last remaining part of a sketch to make things work. In summary, I am comparing two int variables to determine if a motor should turn clockwise or counter clockwise. The variables are cHeading which is the current Heading of the motor as derived from an external voltage, and aBearing which is an Analog bearing taken from a reference in code. When these values are compared to each other, the motor turns clockwise or counter clockwise by switching on a related relay and when the values are equal, the relays open and the motor stops. Each relay has a corresponding "brake" relay which must be on at the same time (sort of a safety mechanism).
Either of these IF statements work, but I cannot find a way to make them work together. For example, the CCW code will go to the proper position and when the values are equal and the motor stops because both relays open. But using both IF statements does not work because they cycle the relays for both CW and CCW.
I have tried nested IF statements, IF Else statements, and arranging the various actions in a different order, all to no avail.
Thanks for any and all advice.
////////////////////COUNTER CLOCKWISE RELAY/////////////////////////////
// High = Relay Off - Low = Relay On
//if cHeading is greater than aBearing then turn on the ccw and brake relay until bearing and heading are the same
if (cHeading > aBearing) {
digitalWrite(relay2, LOW); //BRAKE RELAY ON
digitalWrite(relay4, LOW); //CCW RELAY ON
Serial.println("cHeading is GREATER THAN aBearing SO TURN CCW ");
} else {
if (cHeading <= cHeading) //
digitalWrite(relay4, HIGH); //CCW RELAY OFF
digitalWrite(relay2, HIGH); //BRAKE RELAY OFF
}
////////////////////////CLOCKWISE RELAY/////////////////////////////////
//if cHeading is less than aBearing then turn on the cw relay until bearing and heading are the same
if (cHeading < aBearing) {
digitalWrite(relay1, LOW); //BRAKE RELAY ON
digitalWrite(relay3, LOW); //CW RELAY ON
Serial.println("cHeading is LESS THAN aHeading SO TURN CW ");
} else {
if (cHeading >= aBearing) //
digitalWrite(relay3, HIGH); //CW RELAY OFF
digitalWrite(relay1, HIGH); //BRAKE RELAY OFF
}
This is a common issue, I think, and could be solved by introducing some hysteresis.
Google it, but it's seen in your home thermostat.
The heat does not go on until it is colder by some margin than the setting.
The heat does not go off until it is warmer by some margin than the setting.
Here, by analogy, you should be happy after arriving at the desired angle, or bearing, or whatever, and then then stay there until the error is large enough to warrant further motor activity.
It may be that the desired angle requires more motion in the same direction, so in this sense it is not like the home heating sitch, where the temp starts to go down as soon as you stop heating, but I think the concept will work.
Go until the angle is satisfactory. Stop, wait for the angle to become large enough (that will require tuning and your own idea of how much error is tolerable) and go once again in the direction which will correct for the observed error.
Well you're telling it two different things for each direction. Try something like this pseudo:
if (cHeading > aBearing) {
// turn one way
}
else if(cHeading < aBearing) {
// turn the other way
}
else {
// if it's not greater or less then it must be equal
// so stop turning
}
See with this, if cHeading is less than aBearing then both the first else and the second if both apply. less than is not greater than, so the first else part applies. And then less than IS less than so the if part applies in the second case. And then it's all vice versa if cHeading is greater than aBearing.
if (cHeading > aBearing) {
// turn one way
Serial.println("cHeading is GREATER THAN aBearing SO TURN CCW ");
digitalWrite(relay2, LOW); //BRAKE RELAY ON
digitalWrite(relay4, LOW); //CCW RELAY ON
digitalWrite(relay3, HIGH); //CW RELAY OFF
digitalWrite(relay1, HIGH); //BRAKE RELAY OFF
}
And the other block would turn on and off the opposite relays set.
The do nothing part might be a place to should turn off both relay sets.
This still might benefit from not reacting until the error is more than 0.0001…
@shawninpaso since you posted a snippet, we would have to read carefully to see if you ever said what type the variables are that enter into your decisions.
I am most grateful for the advice from both of you. The code examples provided work as expected but the motor doesn't stop when it is supposed to, even though the sequence of actions with the relays are occurring as they should. Hence, I suspect Alto is on to something with the hysteresis issue.
The values used are 3 digit integers rounded to whole values, e.g. 001 to 185. DC voltage is taken from the motor controller and converted to these values and compared against a static list of values. I'll have to read up on the most effective means to add hysteresis, otherwise I could likely alter the static values which should provide a poor man's version of hysteresis (hope that makes sense).
Sure wish I could post a video here so you could see and hear all of the noise with the motor going back and forth.
I don't know that the source of the numbers is important. But comparing integers means a small difference would make the motor move and it seems might mean frequent reversal.
This would spend on the mechanical properties.
If you wrote
if ((cHeading - aBearing) > someTheshhold) {
someThreshold could be set to inhibit motoring until the error was larger. And a symmetrical expression for motoring the other way.
On the same hand, or is it the other, you may have a control problem that would benefit from a more sophisticated algorithm. This is only something I can name one type of, the PID control loop. PID loops are employed in devices I use but design and tuning of them is outside my ability and experience.
I can't help much anymore, and it seems anyone who could would have to know more about the system you are controlling and your own goals.
is this a typo? won't the condition always be true due to ==
is the following
intended to be
}
else if (cHeading <= cHeading) {
digitalWrite(relay4, HIGH); //CCW RELAY OFF
digitalWrite(relay2, HIGH); //BRAKE RELAY OFF
}
should the logic be
if (cHeading > aBearing) {
digitalWrite(relay2, LOW); //BRAKE RELAY ON
digitalWrite(relay4, LOW); //CCW RELAY ON
Serial.println("cHeading is GREATER THAN aBearing SO TURN CCW ");
}
else {
digitalWrite(relay4, HIGH); //CCW RELAY OFF
digitalWrite(relay2, HIGH); //BRAKE RELAY OFF
}
if (cHeading < aBearing) {
digitalWrite(relay1, LOW); //BRAKE RELAY ON
digitalWrite(relay3, LOW); //CW RELAY ON
Serial.println("cHeading is LESS THAN aHeading SO TURN CW ");
}
else {
digitalWrite(relay3, HIGH); //CW RELAY OFF
digitalWrite(relay1, HIGH); //BRAKE RELAY OFF
}
Thanks for your time and help, Alto. I will explore the threshold idea later today. It certainly seems spot on as the current code actuates the relays correctly but still "slips by" when the motor should come to a full stop.
Thanks, my wording wasn't exactly the best. My failure was trying to abbreviate a longer description of what's going on. The "motor" an antenna rotator and it's associated control box. The control box has three large buttons. The left button must be held down to turn the rotator counter clockwise, the right button make it go clockwise but the middle button must be held down before and with either the left or right button. The middle button releases a brake which keeps the rotator in position and thus won't move until the brake is released. I have hard wired four relays to the control box and found that I needed to use two relays for the brake button to emulate what is done within the control box.
Yes, it's a colossal cut and paste mistake. Thanks for pointing it out as I have caused others to deal with it. Reminds me of a lecture in school a very long time ago about one of the first rocket launches in the USA that failed because a programmer used a comma where he should have used a semi-colon.
Thanks for the sharp observation, my code had already been corrected so it wasn't part of the original issue.
So gc, I just tried the code you so kindly provided.
The logic performs better, but the motor still slips by the intended stopping point, and then goes back and forth seeking it while the relays actuate accordingly. The thing is, this is the first time where the motor has stopped more than once when cHeading is equal to aBearing (which seems huge to me).
Looking at the serial monitor for the data and the voltage values coming off the motor controller, it appears that the cHeading values are lagging behind a bit (slow to "catch up"), which may be the source of the issue, but I'm not completely sure just yet. As you've seen, alto suggested the 'someThreshold' value which may correct for the lagging values but I have not attempted that yet.
someone suggested hysterisis.
maybe something like following, Margin set to 1 deg(?) would be more stable but perhaps not stop the motor precisely
if (cHeading > (aBearing * Margin)) {
}
else if (cHeading == aBearing) {
}
i don't see how PID can be used with a binary output, but perhaps the control needs to engage the brake before reaching the target. when to apply the brake would depend on the rate of change and knowing the effectiveness (rate) of the brake
why would there be a CW and CCW brake? not just a single brake?
Wow, that's great - I'll give it a try this evening.
There is only one brake, but the manner in which it is wired in the control box requires two relays, basically doing the same thing, but it just doesn't work correctly with one relay.
Nor do I, but either something that can learn, or something that can be programmed to account for inertia and other mechanical realities might be the ticket.
Probably the latter easiest first, and it may just be a matter of tuning with a threshold concept but applied also or only during motion
when moving, stop when you are within X of the target.
Thrusters to stationkeeping. All velocity zero. Our inertia should do the job now.
Ah, that's a good point. I think I'll take some measurements and try to account for that difference.
The actual voltages from the controller are in the range of 1.5vdc to 12.5vdc. I'm using a voltage divider to bring that down to something around .5vdc to 3.8vdc .