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:
Frequency must be adjustable
Duty cycle must be from 0.5 to 0.7 or adjustable
Number of pulse (steps) must be adjustable, but not between every trigger. This can be done by connecting to the computer whenever needed.
Input signal is 12v while required pulse output is 5v
Awaiting valuable suggestions
Thanks guys
horace
March 29, 2025, 7:24am
2
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
jim-p
March 29, 2025, 10:32am
3
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..
jim-p
March 29, 2025, 12:30pm
6
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
25 parts
30 parts
50 parts
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
jim-p
March 29, 2025, 1:32pm
9
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();
}
}
}
jim-p
March 29, 2025, 3:45pm
11
cedarlakeinstruments:
AccelStepper
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.
unLike
March 29, 2025, 8:47pm
15
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 I did some quick edits to simplify the code. Hope it at least got the concept across.
tom321
May 6, 2025, 11:17am
17
Hi Do you know how to modify 3 phase generator program to have a phase shift , 0, 90, 150 deg ? Regards tom 321
horace
May 6, 2025, 11:41am
18
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?
tom321
May 6, 2025, 11:50am
19
I need quadrature generator ( 2 channels ) + variable phase shift channel
tom321
May 6, 2025, 11:52am
20
so I need to declare 3 of them ?