Pulse generator with adjustable frequency, duty cycle, number of pulse

Hello

I have a requirement of running stepper motor drivers where a PLC would be an overkill.

I just need to rotate the motor some degrees every time a particular proxy sensor is triggered.

Required flexibility:

  1. Frequency must be adjustable
  2. Duty cycle must be from 0.5 to 0.7 or adjustable
  3. Number of pulse (steps) must be adjustable, but not between every trigger. This can be done by connecting to the computer whenever needed.
  4. Input signal is 12v while required pulse output is 5v

Awaiting valuable suggestions

Thanks guys

this may give you some ideas

// ESP32 three phase square wave variable duty cycle using timers

#define phase1 16  // signal output pins
#define phase2 18
#define phase3 17

unsigned long int period = 10000;                       // timer interval in uSec
unsigned long duty_cycle = 50;                          // percentage
unsigned long pulse_width = period * duty_cycle / 100;  // * 50 / 100;

hw_timer_t *timerperiod = NULL;  // hardware timer
hw_timer_t *timerPhase1 = NULL;  // hardware timer
hw_timer_t *timerPhase2 = NULL;  // hardware timer
hw_timer_t *timerPhase3 = NULL;  // hardware timer

// interrupt service routine to generate HIGH levels
volatile int counter = 0;  // interrupt counter
void ARDUINO_ISR_ATTR onTimer() {
  static byte state = 0;  // determines which phase to generate
  switch (state) {
    case 0:
      digitalWrite(phase1, HIGH);                      // set phase output HIGH
      timerWrite(timerPhase1, 0);                      // clear timer
      timerAlarm(timerPhase1, pulse_width, false, 0);  // generate one shot
      break;
    case 1:
      digitalWrite(phase2, HIGH);
      timerWrite(timerPhase2, 0);
      timerAlarm(timerPhase2, pulse_width, false, 0);  // generate one shot
      break;
    case 2:
      digitalWrite(phase3, HIGH);
      timerWrite(timerPhase3, 0);
      timerAlarm(timerPhase3, pulse_width, false, 0);  // generate one shot
      break;
  }
  if (++state >= 3) state = 0;  // reset state ?
  counter++;
}

// interrupt service routines to generate LOW levels
void ARDUINO_ISR_ATTR onTimerPhase1() {
  digitalWrite(phase1, LOW);  // set phase output LOW
}

void ARDUINO_ISR_ATTR onTimerPhase2() {
  digitalWrite(phase2, LOW);
}

void ARDUINO_ISR_ATTR onTimerPhase3() {
  digitalWrite(phase3, LOW);
}

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println("\n\nESP32 timer three phase square wave with duty Cycle");
  pinMode(phase1, OUTPUT);  // enable phase outputs
  pinMode(phase2, OUTPUT);
  pinMode(phase3, OUTPUT);
  digitalWrite(phase3, 1);
  // setup timer interrupts for 1KHz three phase
  if ((timerperiod = timerBegin(10000000)) == NULL)  // Set timer frequency to 10Mhz
    Serial.println("ERROR! timer period initialisation failed");
  else Serial.println("Timer period initialization OK");
  if ((timerPhase1 = timerBegin(10000000)) == NULL)  // Set timer frequency to 10Mhz
    Serial.println("ERROR! timer Phase 1 initialisation failed");
  else Serial.println("Timer Phase 1 initialization OK");
  if ((timerPhase2 = timerBegin(10000000)) == NULL)  // Set timer frequency to 10Mhz
    Serial.println("ERROR! timer Phase 2 initialisation failed");
  else Serial.println("Timer Phase 2 initialization OK");
  if ((timerPhase3 = timerBegin(10000000)) == NULL)  // Set timer frequency to 10Mhz
    Serial.println("ERROR! timer Phase 3 initialisation failed");
  else Serial.println("Timer Phase 3 initialization OK");
  timerAttachInterrupt(timerperiod, &onTimer);        // Attach timer ISR for period timing
  timerAttachInterrupt(timerPhase1, &onTimerPhase1);  // attch timers ISR for pulse timing
  timerAttachInterrupt(timerPhase2, &onTimerPhase2);
  timerAttachInterrupt(timerPhase3, &onTimerPhase3);
  // Set alarm to call onTimer function every second (value in 10 microseconds).
  // Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter).
  timerAlarm(timerperiod, period / 3, true, 0);
  Serial.println("period increment > (* by 10) decrement < (/ by 10)");
  Serial.println(" duty cycle enter 1 (for 10%) to 9 (for 90%)\n");
}

// display interrupt coun ter every seconds
void loop() {
  static unsigned long int unit = 10000;
  // check if updated period or duty cycle entered
  while (Serial.available()) {  // if characters entered read them
    char ch = Serial.read();
    if (!isprint(ch)) continue;
    if (ch == '>') {
      period += unit;
      if (period / unit >= 10) unit *= 10;
    }
    // < decrement value by 1 unit  - minimum is 1 ?
    if (ch == '<' && period != 1) {
      if (period == unit) unit = unit / 10;
      period -= unit;
    }
    // * increment value by 10 ?
    if (ch == '*') {
      period = unit = unit * 10;
    }
    // / decrement value by 10 - minimum is 1
    if (ch == '/' && period != 1) {
      if (period == unit) unit = unit / 10;
      period = unit;
    }
    if ((ch >= '1') && (ch <= '9')) duty_cycle = (ch - '0') * 10;  // set duty cycle
                                                                   // duty_cycle=50;
    pulse_width = period * duty_cycle / 100;
    Serial.print("period = ");
    Serial.print(period);
    Serial.print(" frequency = ");
    Serial.print(10000000.0 / (period));
    Serial.print(" dutyCycle ");
    Serial.print(duty_cycle);
    Serial.print(" pulse width ");
    Serial.println(pulse_width);
    timerAlarm(timerperiod, period / 3, true, 0);
  }


  // every second print interupt counter
  static unsigned long timert = millis();
  if (millis() - timert >= 1000) {
    Serial.println(counter);
    counter = 0;
    timert = millis();
  }
}

20% duty cycle

80% duty cycle

Why do you need an adjustable duty cycle?
What driver are you using?

Input signal is 12v while required pulse output is 5v

Use a simple voltage divider to convert the 12V to 5V

What Arduino are you using?

In what range?

For most steppers you can use software methods to generate a suitable pulse train. For high rates (e.g > 10k step/sec) using a hardware timer is better.

Also, do you need an acceleration profile?

Thanks for your input good sir

I have no experience with Arduino what so ever

I just want to save money spent on un necessary PLC

Please guide me if I can manage this with very basic knowledge of Arduino which I will try to learn if I understand something here..

You can start by answering my questions.

Adjustable duty cycle is not a requirement. I think 50% is good enough

I will be using Leadshine DM542 driver for a 85kgcm stepper motor. It will ask for a pulse of 5v/0v and a direction signal which is not necessary in this case

I have no idea which Arduino to use.

Pulse frequency required is roughly 5kHz

Main purpose is to divide 1 stepper rotation into

  1. 25 parts
  2. 30 parts
  3. 50 parts
  4. 60 parts

Please ask me if I miss giving any info

Thanks

How is this requirement justified?

Whoyncha just use a stepper library? This controller may not be the one you have, but usually you can make a library work with any of the common parts.

a7

Why do you need such a high speed?
If your motor has 200 steps/rev, then 5000steps/sec will give 1500 RPM.

An arduino (probably any arduino) can manage to do this. The question is where does it become more practical than a PLC? If you already have a PLC in mind that can do this, does it cost less than the time it will take you to develop this system and get it running, along with the hardware costs? That's something that's often missed in the "a PLC is overkill" discussions.

Anyway, here's a simple script that I've used to test motors. Starts running when a switch on pin 2 is tripped. It works with one of the cheap step/direction stepper drivers that you can buy on Amazon. Haven't run it in a while, but it should work as-is (I hope!)

// Demonstrates how to drive stepper motor
//
#include <AccelStepper.h>

// # steps to travel
#define DIST 10000

// Arduino pin definitions
// DIR pin is Arduino Pin 9
#define DIR 9
// STEP pin is Arduino Pin 8
#define STEP 8

#define FWD_PIN 2

void setup()
{
  pinMode(FWD_PIN, INPUT_PULLUP);
  
  myStepper.setMaxSpeed(6000);
  myStepper.setAcceleration(25000);
  Serial.begin(115200);
  Serial.println("Ready");
}

void loop()
{
  bool fwd = !digitalRead(FWD_PIN);

  if (fwd )
  {
    // Go forward
    myStepper.moveTo(DIST);
    myStepper.run();
    
    while (myStepper.isRunning())
    {
        myStepper.run();
    }    
  }
}

From the AccelStepper documentation:
The fastest motor speed that can be reliably supported is about 4000 steps per second at a clock frequency of 16 MHz on Arduino such as Uno etc. Faster processors can support faster stepping speeds.

What processor were you using to be able to do 6000?

This is because the division of 1 circle that I am looking for is not possible with full steps...

I will have to use the microstepping of the drive.

Microstepping means that 200 steps will no longer be 1 rotation.. I will mostly use 1600 steps for 1 rotation

Don't remember. It might have been a Blackpill.
[edit]
Although, I'm fairly sure it was a Nano since I tend to name my non-AVR arduino scripts something that indicates what processor they should run on and this one was just called StepperDemo.

Didn't compile here, needed to declare myStepper. Also I think you need to replace moveTo with move.

If you ask AccelStepper for a higher speed than it can generate, odd things can happen, but it is mostly limited by the acceleration calculation which takes about 250 us per step, giving a step rate around 4kHz. Measured on 16 Mhz Uno.

FastAccelStepper has a much higher performance, but has limits on the number of steppers supported depending on platform. Not a problem for one stepper, provided the platform is supported.

I used this code to run my steppers at various speeds, I use a pro-mini to generate the speeds.

EthernetUDP Udp;
String disc = "";
String diss = "";
const int CS = 10;
int Pulsex = 4900;
int Pulsey = 5200;
int Pulsez = 4900;
unsigned long ts;
unsigned long psx;
unsigned long psy;
unsigned long psz;
int tsta = 0;
int tstb = 0;
int tstc = 0;

int acc = 0;
int echoPin = A2;
int triggerPin = A3;
int maxCentimeters = 500;
SR04 sensor(triggerPin, echoPin);


void setup() {
  Ethernet.init(CS);
  SPI.begin();
  delay(40);
  Ethernet.begin(mac, ip);
  Udp.begin(localPort);
  
    pinMode(5, OUTPUT);// x
    pinMode(6, OUTPUT);// y
    pinMode(9, OUTPUT);// z possibly
  SoftSer.begin(38400);
   //Serial.begin(9600);
}

void loop() {
  updRec();
  recComm();
 ts = micros();
 tsta = int(Pulsex/5);// 20% High Pulse
 if((ts - psx) > tsta && (ts - psx) < Pulsex ){digitalWrite(5, LOW);}
 if((ts - psx) > Pulsex) {psx = ts;digitalWrite(5, HIGH);}
  
 tstb = int(Pulsey/5);// 20% High Pulse
 if((ts - psy) > tstb && (ts - psy) < Pulsey ){digitalWrite(6, LOW);}
 if((ts - psy) > Pulsey) {psy = ts;digitalWrite(6,HIGH);}
  
 tstc = int(Pulsez/5);// 20% High Pulse
 if((ts - psz) > tstc && (ts - psz) < Pulsez ){digitalWrite(9, LOW);}    
 if((ts - psz) > Pulsez) {psz = ts;digitalWrite(9,HIGH);}
  
   }



  void updRec() {
  int packetSize = Udp.parsePacket();
  if (packetSize > 0) {
     Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
     disc = packetBuffer;
     disc.trim();
     
      if(disc.endsWith("SM")){
      Pulsex = disc.toInt();
      Pulsey = Pulsex;
      Pulsez = Pulsex;
      disc = "";
      diss = String(Pulsex)+"SM ";
      udpTrans();
           }
      if(disc.endsWith("SX")){
        Pulsex = disx.toInt();disc = "";
        diss = String(Pulsex) + "SX";
        udpTrans();
        }
      if(disc.endsWith("SY")){
        Pulsey = disc.toInt();disc = "";
        diss = String(Pulsey) + "SY";
        udpTrans();
        }
      if(disc.endsWith("SZ")){
        Pulsez = disz.toInt();disc = "";
        diss = String(Pulsez) + "SZ";
        udpTrans();
        }
            
      if(disc.length() > 2){disc = "";}
    }
    }
    }
    
void udpTrans () {
  
   Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
      if(disc.length() > 0){Udp.println(disc);disc = "";}
      if(diss.length() > 0){Udp.println(diss);diss = "";}
   Udp.endPacket();
    
    
}

It works very well, I got the idea from "Cedarlakeinstruments"
Unlike

Not surprised :frowning: I did some quick edits to simplify the code. Hope it at least got the concept across.

Hi Do you know how to modify 3 phase generator program to have a phase shift , 0, 90, 150 deg ? Regards tom 321

some time since I implemented the 3 phase generator but I don't see why not
maybe just a question of changing the timer interrupt timing
the 120degree timing is set by

timerAlarm(timerperiod, period / 3, true, 0);

what exactly are your requirements?

I need quadrature generator ( 2 channels ) + variable phase shift channel

so I need to declare 3 of them ?