Sequence programming in C/C++ part 2: Memories

Last time I showed you how to program a sequence that is progressed by increasing a counter variable (Part 1), when a condition is fulfilled we increase the counter variable by one to progress the sequence. A similar type of sequence programming is to use memories instead of a counter so that each memory represents a specific step in the sequence.

Just as the counter is increased by one to activate the next step, each step is ended with setting the current memory low and setting the memory for the next step high.

Which one you decide to use has more to do with personal preferences than anything and else in my mind the only difference is that if you do it with memories you need to declare a lot more variables and keep track of all of them. Most sequences I deal with are relatively short (less than ~10 steps) so this is not to much of a problem.

The way we are going to achieve this is with chained if-statements, this is similar to the switch-case structure with one difference. When using a switch-case you declare a variable that you then do a comparison with to activate each case. The allowed data types are integers and characters, this means that it is excellent to use it with a counter since you can have an integer that is increased by one in each case. If we are programming a sequence with memories however we run in to a problem, we have only one variable that is determining which case is active, and since the whole idea with memories is that we have one memory for each sequence step we need to do something different. This is where chained if-statements comes in.

Using chained If-statements

What I mean by “Chained” here is that instead of having a source outside the sequence, like clock pulse in the first part of this series, we will have a variable inside the sequence. So each if-statement will have a measurable outcome, a button being pushed in this case, and that button push will fulfil the requirements for the next if-statement while unfulfilling the requirements for the current statement. I’m not sure if that explanation is clear to you, it sounds weird as I read it, but take a look at the figure below. That is basically what we will program although in C-code.

A short section of ladder code illustrating how sequence programming in a PLC works

As you can see, each step is ended with Resetting the current memory and Setting the next, that is what the S and R coils to the right do. Ladder code is meant to be similar to an electrical diagram so that you can understand it without being a programmer, all you need to know is how to read a circuit diagram, so what you see in the image above is logic built using switches and coils. If you wanted to you could build the actual circuit from buttons, switches, and relays but now I’m a bit of track.

The contraption

Since I am all about automation and process control for industry I wanted to do something that simulates that, the circuit I came up with uses 3 RC servos as a kind of replacement for pneumatic cylinders. Even though pneumatics are being phased out more and more in favour of linear actuators and servos they are still a big part of process automation. As you might have guessed RC servos and pneumatic cylinders/valves don’t share a whole lot in common so it takes some clever work-arounds to get them to share the same logic when it comes to programming.

What I am simulating is the following:

  1. Extend cylinder 1.
  2. Cylinder 1 hits limit switch 1, which stops cylinder 1 and starts cylinder 2.
  3. Extend cylinder 2.
  4. Cylinder 2 hits limit switch 2, which stops cylinder 2 and starts cylinder 3
  5. Extend Cylinder 3.
  6. Cylinder 3 extends and hits limit switch 3, which resets all the cylinders to the start position.

Since I am using RC servos the steps you can exchange the “Extend Cylinder” with “Rotate servo” instead. I also need to stop the servo in the position it is in when the button is activated. But i will show you how i did it.

Initialization

The code is fairly simple since we are only dealing with three sequence steps but it still has a couple of traps for young players, and some not so young. As always we start with declaring all the variables we need in our code.

#include <Servo.h>

Servo servo_1;
Servo servo_2;
Servo servo_3;

bool step_1 = LOW;
bool step_2 = LOW;
bool step_3 = LOW;

bool startButtonState = LOW;
bool lastStartButtonState = LOW;

To use RC servos with our Arduino we need to include the Servo.h library, if you haven’t used it before, don’t worry I will show you. We initiate our servo objects with the Servo command you can see in the image.

To make it as easy as possible for yourself it is always a good idea to give a descriptive mnemonic (name) to your variables and objects, it makes it a lot easier to keep track of what you are trying to do. And if anyone else needs to work with your code they get up to speed faster if you have used descriptive names and been diligent in commenting your code. I gave my servos the unimaginable names servo_1, 2, and 3.

step_1, step_2, and step_3 are the step memories that we will set and reset to control our sequence. The startButtonState and lastStartButtonState are used to trigger the sequence on the falling edge of the button input. You thought I was going to say rising, weren’t you? Just waait, I will explain.

void setup() {
  // put your setup code here, to run once:
  servo_1.attach(9);
  servo_2.attach(10);
  servo_3.attach(11);

  pinMode (buttonPin, INPUT_PULLUP);
  pinMode (limit_1, INPUT_PULLUP);
  pinMode (limit_2, INPUT_PULLUP);
  pinMode (limit_3, INPUT_PULLUP);

The setup

We declare the servo pin with the attach command. Even though we control the servos with PWM it can be any digital output, it doesn’t need to be the ones that are specifically intended for PWM. The servo.attach command also has optional max/min parameters (servo.attach(pin, min, max)) if you are using a non standard servo you should set the max min parameters if you want to write angles directly to the servo and not microseconds.

We declare both our startButton and the limit swithes as inputs with the pinMode function, but to be more precise we are using the INPUT_PULLUP function. This allows us to build the circuit without using any external resistors clamping the input to either ground or 5V when it is not activated. With the INPUT_PULLUP command we activate the internal resistors inside the Arduino that pulls up the pin to 5V when it is not connected to ground by actuating our button or limit switches.

You need to remember that the logic is reversed. This means that instead of triggering on rising edge we are triggering on falling and instead of waiting for an input to go high we are waiting for it to go low.

The void

void loop() {
  
startButtonState = digitalRead(buttonPin);

if(startButtonState != lastStartButtonState){
  if(startButtonState == LOW){
    step_1 = HIGH;
  }
}

Like I said, when the startButton is not the same as lastStartButtonState and it is low we set memory 1 high to start the sequence.

  if (step_1 == HIGH) {
    for (int i = 180; i > 0; i--) {
      if (digitalRead (limit_1) == HIGH) {
        servo_1.write(i);
        delay(50);
      } else {
        i = 0;
        step_1 = LOW;
        step_2 = HIGH;
      }
    }
  }

Rc servos are controlled with PWM signals. The PWM signals have a defined pulse width in µs(Micro seconds) which the servo can translate to angles. On standard servos 1000µs is fully counter-clockwise, 2000µs is fully clockwise, and 1500µs is the midpoint. Many servo manufacturers do not adhere to this standard. It is not uncommon to find servos that respond to anything from 600 to 2400µs.

While you can use the writeMicroseconds() command to specify the precise PWM you want to send to the servo, the servo.h library translates the angle reference 0 to 180 into microseconds as well. However using the angle reference can give some weird angels if you are using a non standard servo.

How a for loop works

The servo is stopped when the limit switch is activated. I accomplish this with a for loop. A for loop runs as many times as you specify in the condition. For example:

for(int i=0; i<4; i++){

doTheThing

}

This for loop would “do the thing” 5 times, we index at 0. Each time through the loop i is increased by 1. When i is more than 4 we break from the loop and continue executing the rest of the code.

How to use a foor loop

In the example we use 180 instead of 5 but otherwise it is the same thing. The integer that determines the amount of times we execute the loop, i, is a local variable. We can use this variable to perform actions inside the loop, in our case we write it to the servo with the write() function. So for each loop we increment the servo 1 degree.

I copied this almost directly from the built in example Sweep in the Arduino IDE. The only difference is that I only write the angle to the servo if the limit switch is not active. If the limit switch is active we set the angle to 0, reset the current sequence step, and set the next sequence step before exiting the loop.

This is what gives the servo the same characteristic as a pneumatic piston, at least if we use a bistable 5/3 valve with a non ventilated center position. I know it is a bot of a stretch but my thoughts move in weird patterns sometimes.

To finish the program we do the same thing 3 more times. In the third and final sequence step we set servo_1 to its start position and reset the step 3 memory. The sequence is now in its idle state and is ready to be executed again.

Final thoughts

Even though it takes some jumping through hoops to program a sequence in text it is pretty straight forward when you get the hang of it. It does help, at least for more complex systems, to write out the sequences in a flowchart first. Then define the different steps in text before before you finally start coding, you avoid some pitfalls that way.

If you want to download the complete source code you can do that here:

ServoSequence.7Z

I haven’t completely decided yet if I am going to write a third part to this series or not so it might take a bit longer than a week for the next part to come out.

Please let me know what you think down in the comments, and please tell me if there is anything in particular you want me to write about.

Till next time.

Posted in All, How To's.

Leave a Reply

Your email address will not be published. Required fields are marked *