Impossible to vary square wave FREQUENCY output based on a PWM input?

I would like to have real-time speed control of a stepper motor using a PWM signal. Basically, I want to be able to control the speed of the stepper motor rotation using an RC controller. The RC receiver sends a PWM (varying duty cycle) to a pin on the Arduino. The Arduino should then map that varying duty cycle from the RC receiver in real time to a varying FREQUENCY PWM signal with constant 50% duty cycle to a digital output pin that will be connected to the STEP pin of the stepper motor driver.
I have posted on different forums and no one has been able to help me figure this out.. so before you tell me that a brushed DC motor or a servo will be easier and work better for my application please hear me out!
Stepper motors easily obtained from printers for free or cheaply purchased. Silent stepper drivers are awesome and cheap and can turn a stepper motor nice and slow with descent torque and absolutely no sound. This is in comparison to servos, which are very noisy (unless you spend >$100) and DC motors spin too fast so you need to buy a gearbox to reduce the speed. These make the motors noisy (unless you spend $200 on a good planetary gearbox).

Everyone tells me to use the following code with the delay time dependent on my PWM input from the receiver:

void loop () {
digitalWrite(stepPin, HIGH);
delayMicroseconds(delayTime);
digitalWrite(stepPin, LOW);
delayMicroseconds(delayTime);
}

This type of code works perfectly fine if you are using an analog input (from a potientiometer for example). HOWEVER, any time I try and use a digital pin as an input, the processor gets tied up and causes a delay following the end of the loop. This results in a nice on/off of the step pin with the correct delay followed by a looong delay that messes up the intended frequency.

I would think that using one of the dedicated timers would be the way to go. Does anyone know how I could go about linking a timer to output a specific starting PWM frequency to a digital pin that can be adjusted every 10-20 milliseconds or so based on changes in the input PWM duty cycle from the RC receiver? This way, a timer can be running on the output pin and any board processing that is tied to the default timer won't interrupt the output frequency. If i want to increase the speed of the motor, the frequency can be stepped up or down at 10-20ms intervals and essentially allow for smooth acceleration and deceleration of the stepper.

I have no clue how to mess with timers though. Please help!!!

Everyone is a moron. Don't listen to him.

Well, there you go.

So one solution would be to transform your incoming PWM signal into a DC signal that varies with the duty cycle of the PWM signal. A simple R/C filter may already get you in that direction - in fact, it may be the solution in itself. Give it a try?

Me too. You could use it to measure the duty cycle of the incoming PWM, provided that the incoming signal is of sufficiently low frequency and your demands on measurement resolution aren't too high. And you could use a timer to generate a PWM signal of adjustable frequency as well. Good thinking!

Yes, it's quite well documented in the datasheet of common microcontrollers. For instance, if you were to us a classic Arduino Nano or UNO, you'd be looking at the Atmega328P datasheet, which has a few chapters dedicated to the use of Timers, including PWM signal generation.
In addition, if you google 'Arduino pwm timer' I think you'll find something. Part of that 'something' is probably also sets of ready-made libraries that you could use.

So my question to you is, really: given your sound reasoning and good ideas so far, what's holding you back!?

The biggest thing holding me back is that I have no coding background and the data sheet for the chip makes no sense to me. Was hoping someone could point me to an example code so I can have something to start with and tweak. If I start from scratch it’s gonna be less of an uphill battle :wink:

I did try and turn the PWM into analog as you mentioned but the problem is that going from Receiver to analog converter to arduino to stepper driver creates lots of noise and really doesn’t translate into anything usable.

Post #2 did, with the suggestion that you google "arduino pwm timer". Have you done that?

There are PWM and timer libraries for popular Arduinos, and they come with examples.

But for someone with no coding background, this will be a challenge. Most people start more simply, and work their way up.

I'd start in a place like this one: https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM
Coincidentally (or not?) it's also the place where your 'everyone' got their poor suggestion from - moreover, it outlines why it's not an optimal solution to do this with digitalWrites & delayMicroseconds. Read on after that bit as it gets interesting.

Well, perhaps if it's done poorly, but in principle the approach could/should work. What kind of noise did you experience, and what kind of approach did you try exactly? Perhaps if you post a schematic of your attempt someone can comment on it.

It can be done purely in hardware. But in order to proceed, you have to tell us what is the input PWM frequency since it will determine whether the Arduino's 16 MHz clock is capable of processing and calculating the duty cycle of the signal. Another caveat is if you are using an UNO, the PWM output frequency will be limited since you'll be using one of the 8 bit timers for the PWM generation.

My solution would be to use the Input Capture Unit of Timer1 to calculate the incoming signal's duty cycle, and use Timer0 for the PWM output

Hi hzrnbgy,
Thanks for your response!

Im working in the Hz range so there is about 6 orders of magnitude cushion between the signal frequency and the processor speed. The duty cycle for the PWM signal from the receiver ranges from 1 to 2ms and the frequency of the signal is 60hz.

To drive the stepper, I need an output signal of ~1.6kHz (fast speed) up to ~70 Hz (slow speed)(edited from original post) to the STEP pin (Im using a TMC2208). Duty cycle of the output signal doesnt matter as far as I know...because I think the stepper driver reads the rising edge of the signal to determine frequency. Keeping a constant 50% duty cycle seems to work fine.

Ill have to read into this timer stuff I guess there is no getting around it.

Can you confirm that the incoming PWM duty cycle is between 6% and 12%? That sounds a bit odd to me.

I had a quick glance at the TMC2208 datasheet, but I'd need to study it more to say something smart...so just one question: have you confirmed experimentally that the 1~70Hz pulse rate is indeed correct?

How much resolution do you need? This is going to determine the feasibility of measuring the 6~12% duty cycle of your 60Hz incoming PWM. Unless of course I misunderstood what you said about a 1~2ms duty cycle; it's kind of odd to express a duty cycle in ms instead of %.

That is because you are using incorrect terminology. A signal with a constant 1:1 m/s ratio is NOT PWM.

This thread my get you started - you will need to change the frequency settings. Start by ignoring the input and just getting a single frequency out.

Since it is a signal from the RC controller, so it a servo or ESC signal.

The servo signal is basically a 20ms period, so it's 50Hz, but as the OP says, it still works at 60Hz.

Also, these HIGH signals fluctuate in the range of approximately 500us to 2500us, which represents the minimum to maximum of the signal.
This signal is correctly "PWM", but few people express it as a percentage of the duty cycle.

Thanks for the explanation @chrisknightley - I'm not experienced with ESC's, but I have read what you said about them. Yes, it makes sense. So my question to OP still stands: what kind of resolution is desired for reading the 1~2ms pulse width from the RC? In principle it's a sufficiently slow signal to allow for decent results by simply measuring the pulse width using an ATMega etc. If nothing much else happens in the sketch besides this and outputting a PWM signal, even polling from the loop and counting time using micros() is likely to give sufficient resolution and no interrupts or timers are needed to measure the pulse width of the incoming signal.

Hi @trentonp

Here's one way to measure an input channel from an RC receiver using the attachInterrupt() function. It's non-blocking, meaning that it continously reads the incoming pulse width around the loop(). Using the micro() function gives the input an accuracy of around 4us on AVR based Arduinos, (1us on ARM based ones):

// Sketch to calculate pulsewidth from an input signal in microseconds

#define INPUT_PIN 10                      // Arduino input pin number

volatile unsigned long isrPulsewidth;     // Define Interrupt Service Routine (ISR) pulsewidth
unsigned long pulseWidth;                 // Define Pulsewidth variable

void setup() 
{
  pinMode(INPUT_PIN, INPUT);              // Set the input pin
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), calcPulsewidth, CHANGE);   // Run the calcPulsewidth function on signal CHANGE
}

void loop() 
{
  noInterrupts();                         // Turn off interrupts
  pulseWidth = isrPulsewidth;             // Copy the isr pulsewidth to the pulsewidth variable
  interrupts();                           // Turn on interrupts
  // Work with pulsewidth here ...
  // ...
}

void calcPulsewidth()                    // Interrupt Service Routine (ISR)
{
  static unsigned long pulseStartTime;   // Start time variable
  
  if (digitalRead(INPUT_PIN) == HIGH)    // If the change was a RISING edge
  {
    pulseStartTime = micros();           // Store the start time (in microseconds)
  }
  else                                   // If the change was a FALLING edge
  {        
    isrPulsewidth = micros() - pulseStartTime;    // Calculate the pulsewidth
  }
}

If you require a resolution of more than 4us, timer 2 can be employed instead of micros() to increase this to 1us if required.

The conversion from 1ms-2ms receiver input pulse to PWM frequency output can be achieved using the Arduino map() and constrain() functions.

Regarding the PWM output, if you're after a 50% duty-cycle that changes frequency, this can be achieved at low frequencies using the Arduino Uno's 16-bit timer 1 in Fast PWM Mode with OCR1A as TOP.

Fast PWM Mode with OCR1A as TOP has the advantage over Clear Timer On Compare Match (CTC) mode, since the OCR1A register is double buffered, meaning that changes to the output only occur on a timer update, or in other words only when the timer overflows. This prevents glitches from appearing on the output waveform.

Here's the code to do that:

// Output 1Hz, 50% duty-cycle on D9 using timer 1 in "Fast PWM mode with OCR1A as TOP"
// Calculate OCR1A value = frequency(CPU) / (2 * precaler * frequency(PWM)) - 1
void setup() 
{ 
  pinMode(9, OUTPUT);                                 // Set D9 as an output                       
  TCCR1A = _BV(COM1A0) | _BV(WGM11) | _BV(WGM10);     // Set timer 1 in Fast PWM mode with OCR1A as TOP
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS12);       // Enable timer clock with divide by 256 prescaler 
  OCR1A = 31249;                                      // 1Hz PWM: OCR1A = 16MHz / (2 * 256 * 1Hz) - 1 = 31249
  //OCR1A = 445;                                        // 70Hz PWM: OCR1A = 16MHz / (2 * 256 * 70Hz) - 1 = 445 
}

void loop() {}

I'd recommend using a stepper library like the AccelStepper library to drive the stepper, no need for any PWM hardware at 70Hz, that's 14285µs per step, ie glacially slow from the point of view of a 16MHz processor.

To time the input waveform using interrupts makes sense as this won't be blocking and allows good accuracy. The input capture unit for timer1 is also a good approach.

1 Like
const byte RCInputPin = 2;
const byte StepPin = 3;
const byte DirectionPin = 4;
const unsigned MinStepRate = 70;
const unsigned MaxStepRate = 1600;

void setup()
{
  pinMode(RCInputPin, INPUT);
  pinMode(StepPin, OUTPUT);
  pinMode(DirectionPin, OUTPUT);
  digitalWrite(DirectionPin, LOW);
}

void loop()
{
  unsigned long inputPulseWidth = pulseIn(RCInputPin, HIGH, 30000);
  if (inputPulseWidth ! 0) // not a timeout
  {
    int stepRate = map(inputPulseWidth, 500, 2500, MinStepRate, MaxStepRate);
    tone(StepPin, stepRate);
  }
}
1 Like

Yes, I generated different pulses and checked speed of motor while STEP pin was attached to the oscilloscope. Max speed was generated by a 1.6kHz signal (sorry I originally said 1Hz-70Hz... Range I need is actually 1600Hz to 70Hz)

image
^the above signal generated the max motor speed that I need

You are 100% correct. Thanks for calling me out on this mistake.

What I was using (some cheap board) didn't have good resolution for measuring the incoming PWM (it was +/- 3-5% so the voltage measured in the output was bouncing up and down and in turn the square wave output of the Arduino was looking like a slinky on the oscilloscope). I didn't take any screenshots so nothing to show but I just ordered a Nano 33 loT which has a DAC (digital to analog conversion) output, so I can do some proper testing and report back with some data.
I still think it makes more sense to use one board to do everything, but I will do some testing with the Nano's DAC as well as trying out all of the other fine suggestions I have gotten regarding timer use.