gsus24
Hello

i try to play around with Beaglebone Black to control a stepper motor.
Now i am interested in on, how to drive the motor using OpenPLC something similar to this



Hope someone could give advise or have some useful links on get started with Motion control?

Thanks and best regards
Quote 0 0
rodrigo_rolle
I found this on a quick search about PLC control for stepping motors, maybe it's gonna be helpful: https://www.researchgate.net/publication/270160733_PLC_AS_A_DRIVER_FOR_STEPPER_MOTOR_CONTROL 

Another idea: maybe ladder logic will be tricky, especially if you're starting PLC coding right now. You should consider using textual languages, such as Structured Text or maybe Function Blocks... All these options are supported by OpenPLC/PLCOpen Editor.

If you manage to make it work, please share it on "OpenPLC Projects" section. This is a very interesting appllication. Good luck! 🙂
Quote 0 0
thiagoralves
A PLC works with scan cycles, which means it is quite challenging (I would say impossible) to drive a stepper motor directly, since these motors require very precise timing. OpenPLC should only be responsible for the high level logic, not low level control. These low level controls must be done using low level languages that have direct hardware access. What can be done instead is to write a stepper motor driver on OpenPLC hardware layer code box, and use this to drive the motor. 
Quote 0 0
gsus24
I will first give a try with timers like this.
https://www.sanfoundry.com/plc-program-perform-pulse-width-modulation/
The maximum frequency depents on the cycle time. I have read cycle time on Raspberry Pi is about 10ms and will be the maximum frequency of PWM Output.

Is the cycle time constant or depents it on the complexity of the program?
I think this will affect the precision of the signal.


Could you please tell me more about the Driver on hardware layer?
Will be this C++ code will be helpfull?
https://github.com/derekmolloy/beaglebone/blob/master/EasyDriver.cpp

Thanks alot for your great tips.
  
Quote 0 0
thiagoralves
The cycle time is defined by the task scheduling of your program. You change it inside PLCOpen Editor. Open your program on PLCOpen Editor, and on the left panel you go to Config0 -> Res0. Under Tasks you will see the task that is driving you main program. It is probably around 50ms or 100ms. If you change that number, it will affect how often your main program is called, or in other words, your cycle time. Using a very short cycle time can lead to 100% CPU usage and instability, so beware.

That's why it is more wise to write precise control algorithms using hardware instead of software. OpenPLC is a quite heavy abstraction layer on top of the hardware, which means it won't give you fine timing for you algorithm. To interface directly with the hardware, you need to use the hardware layer code box. Here is some generic explanation about it that I gave a few months ago:

"I believe all the required instructions for how ithe hardware layer code box works are on the comments of the code itself. When you open the Hardware page it loads the contents of the custom_layer.cpp file. Read the comments on that file carefully and you will have an idea of how it works. To clarify more on the subject, here is an explanation of the custom layer functions:

- void initCustomLayer()
This function is called only once, when OpenPLC runtime is starting. You should put in there all your initialization code, like starting up your 1-Wire hardware, setting up your sensors, initializing global variables, etc...

- void updateCustomIn()
This function is called in a loop at the beginning of the scan cycle. It should be used to update OpenPLC's internal input buffers. Since you want to implement 1-Wire sensors, it should be appropriate to put the readings from the sensors into the input buffers. So on this function you should put the code to actually read the sensors and then insert those readings into a position in the input buffer. The input buffer is linearly mapped with OpenPLC variables. So, for example:
int_input[0] = %IW0
int_input[1] = %IW1
bool_input[0][2] = %IX0.2
... and so on

To avoid writting into null pointers, you must check first if that position on the buffer exists before writing to it. Therefore, the right code to insert something on the input buffer is:

if (int_input[3] != NULL) *int_input[3] = rfid_value;

This code inserts the integer "rfid_value" into %IW3.

- void updateCustomOut()
Similarly, this function is called at the end of the scan cycle and should update your custom outputs based on the output buffers. Since all you want to implement is an RFID reader, it seems that you don't have any outputs to work with, so you can leave this function empty.

Finally, the ignored vectors should be used only if you're planning to overwrite some of your current hardware inputs or outputs. For example, imagine you are running on a PiXtend board and you don't want %IW0 to be attached to PiXtend's internal analog input. You can have %IW0 added to the ignored vectors and then the PiXtend driver won't read that particular input anymore. Then you can write your own custom code to insert whatever you want in %IW0. If you don't add %IW0 to the ignored vectors and try to manipulate it with your custom code, both PiXtend analog input reading and your code will compete for %IW0 causing a race condition. In the same way, if you write your custom layer code to manipulate some unused input or output, like %IW40, you don't have to add it to the ignored vectors, as the hardware layer driver is not working with it anyway.
Quote 0 0
gsus24
I get it work on BeagleBone Black. Installed all needed packages and openplc with custom setting.
What i did was, copy the c++ and header files(EasyDriver.cpp/h, SimpleGPIO.cpp/h) from Derek Molloy´s tutorial to  …/webserver/core/
and edit the hardwarelayer box in browser to this

#include "ladder.h"
#include "SimpleGPIO.h"
#include "EasyDriver.h"
#include <pthread.h>

//-----------------------------------------------------------------------------
// DISCLAIMER: EDDITING THIS FILE CAN BREAK YOUR OPENPLC RUNTIME! IF YOU DON'T
// KNOW WHAT YOU'RE DOING, JUST DON'T DO IT. EDIT AT YOUR OWN RISK.
//
// PS: You can always restore original functionality if you broke something
// in here by clicking on the "Restore Original Code" button above.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// These are the ignored I/O vectors. If you want to override how OpenPLC
// handles a particular input or output, you must put them in the ignored
// vectors. For example, if you want to override %IX0.5, %IX0.6 and %IW3
// your vectors must be:
//     int ignored_bool_inputs[] = {5, 6}; //%IX0.5 and %IX0.6 ignored
//     int ignored_int_inputs[] = {3}; //%IW3 ignored
//
// Every I/O on the ignored vectors will be skipped by OpenPLC hardware layer
//-----------------------------------------------------------------------------
int ignored_bool_inputs[] = {-1};
int ignored_bool_outputs[] = {-1};
int ignored_int_inputs[] = {-1};
int ignored_int_outputs[] = {-1};
#define MAX_OUTPUT     1
#define MAX_INPUT     1

//outBufferPinMask: pin mask for each output, which
//means what pin is mapped to that OpenPLC output
int outBufferPinMask[MAX_OUTPUT] =  { 65 };
int inBufferPinMask[MAX_INPUT] =  { 27 };
//gpio_MS1, gpio_MS2, gpio_STEP, gpio_SLP, gpio_DIR, rpm speed, steps per revolution
EasyDriver motor1(66, 67, 69, 68, 26, 44, 180, 48); //gpio pins (last two arguments not necessary as they are the default values)
pthread_t step_thread;

void *stepThread(void *parent) 

       //In case the motor SLP (sleep) pin is low
  motor1.wake();
  
  //forward single step
  motor1.rotate(360);
        motor1.sleep();                  //easy to move motor shaft while sleep is enabled
           //unexport of SLP pin can set this low, meaning shaft
           //torque is high when finished

        return NULL;

 } 

//-----------------------------------------------------------------------------
// This function is called by the main OpenPLC routine when it is initializing.
// Hardware initialization procedures for your custom layer should be here.
//-----------------------------------------------------------------------------
void initCustomLayer()
{
        //set pins as output
  for (int i = 0; i < MAX_OUTPUT; i++)
  {
          gpio_export(outBufferPinMask[i]);    // The LED
                gpio_set_dir(outBufferPinMask[i], OUTPUT_PIN);   // The LED is an output

  }
        //set pins as input
  for (int i = 0; i < MAX_INPUT; i++)
  {
        gpio_export(inBufferPinMask[i]);    // The LED
              gpio_set_dir(inBufferPinMask[i], INPUT_PIN);   // The LED is an output
              //gpio_set_edge(inBufferPinMask[i], "rising");
  }
              
  
}
//-----------------------------------------------------------------------------
// This function is called by OpenPLC in a loop. Here the internal input
// buffers must be updated with the values you want. Make sure to use the mutex 
// bufferLock to protect access to the buffers on a threaded environment.
//-----------------------------------------------------------------------------
void updateCustomIn()
{
    // Example Code - Overwritting %IW3 with a fixed value
    // If you want to have %IW3 constantly reading a fixed value (for example, 53)
    // you must add %IW3 to the ignored vectors above, and then just insert this 
    // single line of code in this function:
    //     if (int_input[3] != NULL) *int_input[3] = 53;
//INPUT
        
  for (int i = 0; i < MAX_INPUT; i++)
  {
           if (bool_input[i/8][i%8] != NULL) *bool_input[i/8][i%8] = gpio_get_value(inBufferPinMask[i]);
           //gpio_get_value(inBufferPinMask[i], *bool_input[i/8][i%8]);

  }
       
       
               
}
//-----------------------------------------------------------------------------
// This function is called by OpenPLC in a loop. Here the internal output
// buffers must be updated with the values you want. Make sure to use the mutex 
// bufferLock to protect access to the buffers on a threaded environment.
//-----------------------------------------------------------------------------
void updateCustomOut()
{
    // Example Code - Sending %QW5 value over I2C
    // If you want to have %QW5 output to be sent over I2C instead of the
    // traditional output for your board, all you have to do is, first add
    // %QW5 to the ignored vectors, and then define a send_over_i2c()
    // function for your platform. Finally you can call send_over_i2c() to  //In case the motor SLP (sleep) pin is low
  
    // send your %QW5 value, like this:
    //     if (int_output[5] != NULL) send_over_i2c(*int_output[5]);
    //
    // Important observation: If your I2C pins are used by OpenPLC I/Os, you
    // must also add those I/Os to the ignored vectors, otherwise OpenPLC
    // will try to control your I2C pins and your I2C message won't work.
        
    //OUTPUT
  for (int i = 0; i < MAX_OUTPUT; i++)
  {
            gpio_set_value(outBufferPinMask[i], *bool_output[i/8][i%8]);

  }
       if(gpio_get_value(inBufferPinMask[0]))
       {
            /* Create child thread that will perform some task. */
            //pthread_create(&step_thread, NULL, stepThread, NULL);
        //In case the motor SLP (sleep) pin is low
  motor1.wake();
  
  //forward single step
  motor1.rotate(360);
    motor1.sleep();                  //easy to move motor shaft while sleep is enabled
           //unexport of SLP pin can set this low, meaning shaft
           //torque is high when finished



            
         
       }


I took the HelloWorld.xml example with button and lamp
If button is pressed i trigger the Motor in hardwarelayer box to rotate 360 degrees.

How to do the Motor stuff in seperate thread? I tried it with the Code below, but don´t work.
Quote 0 0
thiagoralves
For your thread to work you need to start it somewhere. The best place would be inside the void initCustomLayer() function. You can check the modbus_master.cpp code to see how I implemented threads. This file is responsible for querying I/O from external slave devices. You will see that the I/O query happens on a different thread.
Quote 0 0