Complex PWM - suggestions?

I'd like to use the Nano Every in a project to control a pair of high speed solenoid valves, and I'd welcome suggestions on how to implement it.

  • Each valve will be controlled by a PWM signal on one digital pin.
  • Frequency to be variable from ~1Hz to ~300Hz.
  • PWM signals will be non-overlapping.
  • Duty cycle of each PWM signal to be controllable independently and variable from 0 - 50%.

This should explain things more clearly...

I think the best option is to use TCA in 16-bit single-slope PWM mode. Things I'm not sure about are (a) will this mess up other stuff in the Arduino? (I know it'll mess up the timing functions but I can live without those), and (b) how to achieve the alternating pulses of different durations. Maybe CCL could be used to do that.
I could implement the PWM in software but I want this device to be controllable by commands on the serial port and I'd be concerned about the time it takes to handle serial comms interfering with signal timing.

Before I dive in and maybe go wandering up a blind alley or two I'd welcome any suggestions and pointers to existing code that might be useful.

The Nano Every has a ATmega4809 and has the millis interrupt and millis() function here: https://github.com/arduino/ArduinoCore-megaavr/blob/master/cores/arduino/wiring.c#L85.
It uses TimerB0 (see below). If you stop millis(), then you can not use the Arduino environment (unless you know everything about Arduino). Many functions rely on millis().

Do you want a variable frequency or can the frequency be fixed in the range of 0...300Hz ?
Why can the valves not deal with overlapping signals ?
Do you have a link to those valves ?

I have forgotten about the PWM modes, but others can make such signals with the hardware timers.

Hello chrisdancer

Take four coupled micro() funktion based timer to generate the variable PWM from ~1Hz to ~300Hz.

Have a nice day and enjoy coding in C++.

According to the blog below Timer B0 is used for one of the PWM outputs. Timer B3 is used for millis(), micros() and delay().
I have seen examples which change timer settings causing millis(), micros() and delay() to stop working properly, but the Arduino environment still works fine. As I understand it some libraries rely on these functions for timing.

Variable frequency, controllable by commands sent down the serial port. Frequency will not be varied while signals are being output.

They are being used to alternately inflate and deflate a pneumatic actuator; if they are both on together air will just bleed straight through.

With so low frequencies you don't need a hardware PWM. Just setup any unused MCU timer for desired frequency and turn your GPIO on and off in interrupt.
The work of millis() and micros() will not affected.

Update: I'm now leaning towards using the microseconds counter, polling it in a free-running loop. At the start of the first cycle the program will set output A, and read the micros() value and use it to calculate 4 event times:

  • Pulse A end time
  • Pulse B start time
  • Pulse B end time
  • Next cycle start time
    Then in the loop read micros() and if we passed one of those events, set or reset the appropriate port line and if necessary start the next cycle.
    Handling serial comms will cause a little jitter in the pulse edges but that's acceptable.
1 Like

That sounds like you are using micros() to calculate future time stamps. That is a no-no as far as micros() rollover, you will see timing errors if you do that. But I would have to see your code to be sure.

it is also possible, quite a reasonable approach

O, I was wrong. Sorry for that. In "boards.txt" the "MILLIS_USE_TIMERB3" is indeed defined.

If you stop millis() and then run into problems, then we can not help you :cry: It is not just a few libraries, it is really a part of the Arduino system.

A valve that can switch at 1000Hz ? Now I understand the variable frequency and the problem with overlapping.

I agree, using a micros() timer is a good start.
If I have some spare time and you have a test sketch, then perhaps I can use a Logic Analyzer for the signals in the Wokwi simulator.

1 Like

Here's a test of the double width PWM using the micros() function. It works well; loop time is about 20us. I'm using unsigned longs for all time values so (I think) overrun of the micros() value should not cause a problem.

Here's a scope trace:

And the code:

/*
Pneumatic vibrator v.1.00 2023
*/

#define va 2
#define vb 3

unsigned long timenow;
unsigned long overrun;
unsigned long per_ab = 10000;   //Period ab = 10ms (100Hz)
unsigned long dur_a = 3000;     //Duration a = 3ms
unsigned long dur_b = 4000;     //Duration b = 4ms
unsigned long half_per_ab;
unsigned long t_a_start;
unsigned long t_a_end;
unsigned long t_b_start;
unsigned long t_b_end;

boolean ledstate = false;
boolean start = true;
boolean run_ab = false;

void setup() {

  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(va, OUTPUT);
  pinMode(vb, OUTPUT);

  Serial.begin(9600);

  half_per_ab = per_ab >> 1;
}

void loop() {

timenow = micros();
overrun = timenow - t_a_start;
if ( bitRead(overrun, 31) == 0 ) {                   //If we're past the end of the cycle:
    digitalWrite(va, true);                         //Valve A on
    t_a_start = timenow + per_ab - overrun;         //Set times for new cycle
    t_a_end = timenow + dur_a - overrun;
    t_b_start = timenow + half_per_ab - overrun;
    t_b_end = t_b_start + dur_b;
}
else if ( ( bitRead( (timenow - t_b_end), 31) ) == 0 ) {   //If we're past the end of pulse B:
  digitalWrite(vb, false);                            //Valve B off
}
else if ( ( bitRead( (timenow - t_b_start), 31) ) == 0 ) {   //If we're past the start of pulse B:
  digitalWrite(vb, true);                            //Valve B on
}
else if ( ( bitRead( (timenow - t_a_end), 31) ) == 0 ) {   //If we're past the end of pulse A:
  digitalWrite(va, false);                            //Valve A off
}

// Serial port section goes here

  digitalWrite(LED_BUILTIN, ledstate);
  ledstate = !ledstate;
}

If you add more code, then there might be more jitter/delay in the output.

Wokwi does not have the Nano Every, so I used a Arduino Uno.
I have PulseView installed on my computer and Wokwi has a simulated Logic Analyzer which stores the data on my computer.
PulseView has decoders to analyze the signals:

This is the Wokwi project that I used:

So far, no one did join this topic to write code for the hardware timers :cry:

1 Like

Found an oscilloscope and hooked it up to @Koepel 's example ...

2 Likes

Yes there will be, although it is not too important in this application. A stable and accurate frequency is more important, and using the overrun variable (="how late am I?") takes care of that.

Thanks for introducing me to Wokwi. Debugging code on Arduino is a PITA, so I'm sure I'll be using it a lot!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.